我最近處理了一個類似的問題 - 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";
}
}
您是否對如何使其與Symfony2一起工作有任何想法?具體來說,「$ child-> getConfig() - > getOptions();」在2.0中不可用,因此我無法獲得表單的原始選項。如果我離開選項,我最終會在類「Doctrine \ ORM \ PersistentCollection」中存在「Both屬性」0「,方法」get0()「和方法」is0()「 – CriticalImpact 2012-08-03 07:22:22
@CriticalImpact我已經通過2.0表單組件的源代碼,我看不出有什麼辦法可以真正達到相同的效果(這些選項並不是長期存儲的)。如果你可以生活,你也許可以做到總是使用默認選項 - 爲了解決上面得到的錯誤,您應該只需要適當地設置property_path(不幸的是,我不得不留下它來發現什麼格式2.0用於集合中的屬性路徑 - 一個var_dump的幾個應該做的伎倆,雖然) – 2012-08-03 11:51:44
我真的設法想出另一個解決方案。我添加了FormEvents :: PRE_SET_DATA事件監聽器,得到了支持對象(在我的情況下是一個問題對象),確定了類型這個問題(我在我的問題中設置了一些東西說是否它的複選框,是/否,文本字段等),然後將該字段添加到基於問題對象中設置的類型的窗體。 – CriticalImpact 2012-08-08 05:58:45