我最近处理了一个类似的问题 - Symfony本身对多态集合并没有任何让步,但很容易使用EventListener为它们提供支持来扩展表单。
下面是我的事件监听,它采用了类似的方法的Symfony \分量\表格\延期\核心\事件监听\ ResizeFormListener,事件侦听器提供了收集表型正常功能的内容:
namespace Acme\VariedCollectionBundle\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
class VariedCollectionSubscriber implements EventSubscriberInterface
{
protected $factory;
protected $type;
protected $typeCb;
protected $options;
public function __construct(FormFactoryInterface $factory, $type, $typeCb)
{
$this->factory = $factory;
$this->type = $type;
$this->typeCb = $typeCb;
}
public static function getSubscribedEvents()
{
return array(
FormEvents::PRE_SET_DATA => 'fixChildTypes'
);
}
public function fixChildTypes(FormEvent $event)
{
$form = $event->getForm();
$data = $event->getData();
// Go with defaults if we have no data
if($data === null || '' === $data)
{
return;
}
// It's possible to use array access/addChild, but it's not a part of the interface
// Instead, we have to remove all children and re-add them to maintain the order
$toAdd = array();
foreach($form as $name => $child)
{
// Store our own copy of the original form order, in case any are missing from the data
$toAdd[$name] = $child->getConfig()->getOptions();
$form->remove($name);
}
// Now that the form is empty, build it up again
foreach($toAdd as $name => $origOptions)
{
// Decide whether to use the default form type or some extension
$datum = $data[$name] ?: null;
$type = $this->type;
if($datum)
{
$calculatedType = call_user_func($this->typeCb, $datum);
if($calculatedType)
{
$type = $calculatedType;
}
}
// And recreate the form field
$form->add($this->factory->createNamed($name, $type, null, $origOptions));
}
}
}
使用这种方法的缺点是,为了识别提交时多态实体的类型,您必须在绑定表单之前在相关实体上设置表单上的数据,否则,侦听器无法确定哪种类型数据真的是。你可以用FormTypeGuesser系统解决这个问题,但这超出了我的解决方案的范围。
同样,虽然使用此系统的集合仍然支持添加/删除行,但它会假设所有新行都是基本类型 - 如果您尝试将它们设置为扩展实体,它会给您一个错误关于包含额外字段的表单。
为了简单起见,我使用的方便型封装这个功能 - 参见下面的这一点,一个例子:
namespace Acme\VariedCollectionBundle\Form\Type;
use Acme\VariedCollectionBundle\EventListener\VariedCollectionSubscriber;
use JMS\DiExtraBundle\Annotation\FormType;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\AbstractType;
/**
* @FormType()
*/
class VariedCollectionType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
// Tack on our event subscriber
$builder->addEventSubscriber(new VariedCollectionSubscriber($builder->getFormFactory(), $options['type'], $options['type_cb']));
}
public function getParent()
{
return "collection";
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setRequired(array('type_cb'));
}
public function getName()
{
return "varied_collection";
}
}
实施例: 命名空间的Acme \ VariedCollectionBundle \表;
use Acme\VariedCollectionBundle\Entity\TestModelWithDate;
use Acme\VariedCollectionBundle\Entity\TestModelWithInt;
use JMS\DiExtraBundle\Annotation\FormType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\AbstractType;
/**
* @FormType()
*/
class TestForm extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$typeCb = function($datum) {
if($datum instanceof TestModelWithInt)
{
return "test_with_int_type";
}
elseif($datum instanceof TestModelWithDate)
{
return "test_with_date_type";
}
else
{
return null; // Returning null tells the varied collection to use the default type - can be omitted, but included here for clarity
}
};
$builder->add('demoCollection', 'varied_collection', array('type_cb' => $typeCb, /* Used for determining the per-item type */
'type' => 'test_type', /* Used as a fallback and for prototypes */
'allow_add' => true,
'allow_remove' => true));
}
public function getName()
{
return "test_form";
}
}
您是否对如何使其与Symfony2一起工作有任何想法?具体来说,“$ child-> getConfig() - > getOptions();”在2.0中不可用,因此我无法获得表单的原始选项。如果我离开选项,我最终会在类“Doctrine \ ORM \ PersistentCollection”中存在“Both属性”0“,方法”get0()“和方法”is0()“ – CriticalImpact 2012-08-03 07:22:22
@CriticalImpact我已经通过2.0表单组件的源代码,我看不出有什么办法可以真正达到相同的效果(这些选项并不是长期存储的)。如果你可以生活,你也许可以做到总是使用默认选项 - 为了解决上面得到的错误,您应该只需要适当地设置property_path(不幸的是,我不得不留下它来发现什么格式2.0用于集合中的属性路径 - 一个var_dump的几个应该做的伎俩,虽然) – 2012-08-03 11:51:44
我真的设法想出另一个解决方案。我添加了FormEvents :: PRE_SET_DATA事件监听器,得到了支持对象(在我的情况下是一个问题对象),确定了类型这个问题(我在我的问题中设置了一些东西说是否它的复选框,是/否,文本字段等),然后将该字段添加到基于问题对象中设置的类型的窗体。 – CriticalImpact 2012-08-08 05:58:45