2012-07-16 79 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