vce*_*ick 11 polymorphism doctrine symfony
我正在玩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;
...
}
Run Code Online (Sandbox Code Playgroud)
很简单,但是当我为我的订单类创建一个表单时
class OrderType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add('items', 'collection', array('type' => AbstractOrderItemType()));
}
}
Run Code Online (Sandbox Code Playgroud)
我不确定如何处理这种情况,你有效地需要为集合中的每个项目类别使用不同的表单类型?
我最近解决了一个类似的问题 - 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));
}
}
}
Run Code Online (Sandbox Code Playgroud)
使用这种方法的缺点是,为了识别提交时多态实体的类型,必须在绑定之前使用相关实体在表单上设置数据,否则侦听器无法确定数据的类型是.您可以使用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";
}
}
Run Code Online (Sandbox Code Playgroud)
示例:namespace 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";
}
}
Run Code Online (Sandbox Code Playgroud)