2012-07-16 76 views
11

我正在玩弄Symfony2和Im,并不确定Symfony2如何处理View组件中的Polymorphic集合。看来我可以用AbstractChildren集合创建一个实体,但不知道如何在Form Type类中使用它。Symfony2 Forms和Polymorphic collections

例如,我有以下实体关系。

/** 
* @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; 
    ... 
} 

足够简单,但是当IM创建形式为我的订单班

class OrderType extends AbstractType 
{ 
    public function buildForm(FormBuilder $builder, array $options) 
    { 
     $builder->add('items', 'collection', array('type' => AbstractOrderItemType())); 
    } 
} 

我不确定如何处理这种情况下,你需要有效形式不同类型在每一类项目采集?

回答

9

我最近处理了一个类似的问题 - 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"; 
    } 
} 
+0

您是否对如何使其与Symfony2一起工作有任何想法?具体来说,“$ child-> getConfig() - > getOptions();”在2.0中不可用,因此我无法获得表单的原始选项。如果我离开选项,我最终会在类“Doctrine \ ORM \ PersistentCollection”中存在“Both属性”0“,方法”get0()“和方法”is0()“ – CriticalImpact 2012-08-03 07:22:22

+0

@CriticalImpact我已经通过2.0表单组件的源代码,我看不出有什么办法可以真正达到相同的效果(这些选项并不是长期存储的)。如果你可以生活,你也许可以做到总是使用默认选项 - 为了解决上面得到的错误,您应该只需要适当地设置property_path(不幸的是,我不得不留下它来发现什么格式2.0用于集合中的属性路径 - 一个var_dump的几个应该做的伎俩,虽然) – 2012-08-03 11:51:44

+2

我真的设法想出另一个解决方案。我添加了FormEvents :: PRE_SET_DATA事件监听器,得到了支持对象(在我的情况下是一个问题对象),确定了类型这个问题(我在我的问题中设置了一些东西说是否它的复选框,是/否,文本字段等),然后将该字段添加到基于问题对象中设置的类型的窗体。 – CriticalImpact 2012-08-08 05:58:45

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 
    } 
} 

在您的订单类型的表单类中添加这两种形式的创建不同的表单类,像这样

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,ProductOrder键入主表单OrderType。因此,如果您初始化此表单,稍后在控制器中,您将获得OrderType和OrderType的所有字段。

我希望这回答你的问题,如果仍然不清楚,请通过文档在这里嵌入多个表单。 http://symfony.com/doc/current/cookbook/form/form_collections.html

+2

从这个解决方案,虽然我不能只有一个产品和/或单一订阅? Id倾向于收集可能是产品或订阅的对象的集合,并让Symfony决定哪些表单类型适合于集合中的实体 – vcetinick 2012-07-25 01:10:02