我正在使用Symfony2和Im abit不确定Symfony2如何在View组件中处理Polymorphic集合。看来我可以创建一个包含AbstractChildren集合的实体,但不知道如何在Form Type类中使用它。
例如,我有以下实体关系。
/**
* @ORM\Entity
*/
class Order
{
/**
* @ORM\OneToMany(targetEntity="AbstractOrderItem", mappedBy="order", cascade={"all"}, orphanRemoval=true)
*
* @var AbstractOrderItem $items;
*/
$orderItems;
...
}
/**
* Base class for order items to be added to an Order
*
* @ORM\Entity
* @ORM\InheritanceType("JOINED")
* @ORM\DiscriminatorColumn(name="discr", type="string")
* @ORM\DiscriminatorMap({
* "ProductOrderItem" = "ProductOrderItem",
* "SubscriptionOrderItem " = "SubscriptionOrderItem "
* })
*/
class AbstractOrderItem
{
$id;
...
}
/**
* @ORM\Entity
*/
class ProductOrderItem extends AbstractOrderItem
{
$productName;
}
/**
* @ORM\Entity
*/
class SubscriptionOrderItem extends AbstractOrderItem
{
$duration;
$startDate;
...
}
很简单,但是我为我的订单类创建了一个表单
class OrderType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add('items', 'collection', array('type' => AbstractOrderItemType()));
}
}
我不确定如何处理这种情况,你有效地需要为集合中的每个项目类别使用不同的表单类型?
答案 0 :(得分:9)
我最近解决了类似的问题 - Symfony本身对多态集合没有任何让步,但是使用EventListener扩展表单很容易为它们提供支持。
以下是我的EventListener的内容,它使用与Symfony \ Component \ Form \ Extension \ Core \ EventListener \ 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 \ Form;
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";
}
}
答案 1 :(得分:0)
在您给出的示例中,您必须为那些ProductOrder和SubscriptionOrder创建不同的表单类
class ProductOrderType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
//Form elements related to Product Order here
}
}
和
class SubsciptionOrderType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
//Form elements related SubscriptionOrder here
}
}
在OrderType表单类中,您可以添加这些表单,例如
class OrderType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add('product',new ProductOrderType())
$builder->add('subscription',new SubsciptionOrderType())
//Form elements related to order here
}
}
现在,这将两个表单SubsciptionOrderType,ProductOrderType添加到主窗体OrderType。因此,如果您稍后在控制器中初始化此表单,您将获得订阅和产品表单的所有字段与OrderType的字段。
我希望如果仍然不清楚,请回答您的问题,请在此处查看嵌入多个表单的文档。 http://symfony.com/doc/current/cookbook/form/form_collections.html