2012-07-16 11 views
11

Symfony2とImでは、Symfony2がどのようにViewコンポーネントのポリモフィックコレクションを扱うのか不明です。私はAbstractChildrenのコレクションを持つエンティティを作成できますが、Form Typeクラスの中でどのように行うかについてはわかりません。Symfony2フォームとポリモーフィックコレクション

たとえば、次のエンティティの関係があります。

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

私はあなたが効果的に内のアイテムのクラスごとに異なるフォームタイプを必要とするこのような状況に対処する方法がわかりませんよコレクション?あなたはこれらの形態の両方を追加したものproductOrderはとSubscriptionOrderのためにあなたのOrderTypeフォームクラスで

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

を別のフォームクラスを作成しなければならないあなたが与えるれている例では

答えて

9

最近、私は同じような問題に取り組みました.Symfony自体は多態性コレクションの譲歩をしませんが、フォームを拡張するためにEventListenerを使用すると簡単にサポートできます。

以下

のSymfony \コンポーネント\フォーム\拡張\コア\のEventListener \ ResizeFormListener、コレクションのフォームタイプの通常の機能を提供してイベントリスナーに似たアプローチを採用しています私のEventListenerの内容です:

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

例: 名前空間アクメ\ 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"クラスには "最終的にプロパティ" 0 "もメソッド" get0() "もメソッド" is0() "も存在しません – CriticalImpact

+0

@CriticalImpact 2.0 Formコンポーネントのソースであり、実際には同じ効果を実現する方法は見当たりません(オプションは長期間保存されません)。常にデフォルトのオプションを使用しています - 上記のエラーを解決するには、property_pathを適切に設定する必要があります(残念ながら、コレクションのプロパティパスに2.0が使用する形式 –

+2

私はFormEvents :: PRE_SET_DATAのイベントリスナーを追加し、バッキングオブジェクト(私の場合は質問オブジェクト)を取得し、タイプを決定しました。質問(私は何かが私のキューに入っているチェックボックス、はい/いいえ、テキストフィールドなど)を入力し、質問オブジェクトに設定されているタイプに基づいてフォームにフィールドを追加します。 – CriticalImpact

0

、このように

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のフィールドで取得されます。

まだ明確でない場合は、複数のフォームを埋め込むためのドキュメントをご覧ください。 http://symfony.com/doc/current/cookbook/form/form_collections.html

+2

このソリューションからは、単一の製品および/または単一のサブスクリプションしか使用できませんでしたか? Idは、製品やサブスクリプションである可能性があるオブジェクトのコレクションを持ち、Symfonyがコレクション内のエンティティに適したフォームタイプを決定させることを好みます。 – vcetinick

関連する問題