Ich habe vor kurzem ein ähnliches Problem angegangen - Symfony selbst macht keine Zugeständnisse für polymorphe Sammlungen, aber es ist einfach, sie mit einem EventListener zu unterstützen, um das Formular zu erweitern.
Unten ist der Inhalt meiner Eventlistener, die Symfony \ Component \ Formular \ Extension \ Core \ Eventlistener \ ResizeFormListener, den Ereignis-Listener, der die Form Typ der normalen Funktionalität Sammlung bietet einen ähnlichen Ansatz verwendet:
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));
}
}
}
Der Nachteil dieses Ansatzes besteht darin, dass für die Erkennung der Typen Ihrer polymorphen Entitäten beim Senden die die Daten in Ihrem Formular mit den relevanten Entitäten vor dem Binden festlegen müssen, andernfalls hat der Listener keine Möglichkeit, festzustellen, um welchen Typ es sich handelt Die Daten sind wirklich. Sie könnten diese Arbeit mit dem FormTypeGuesser-System möglicherweise umgehen, aber das war nicht der Rahmen meiner Lösung.
Während eine Sammlung, die dieses System verwendet, weiterhin das Hinzufügen/Entfernen von Zeilen unterstützt, wird davon ausgegangen, dass alle neuen Zeilen vom Basistyp sind. Wenn Sie versuchen, sie als erweiterte Entitäten einzurichten, erhalten Sie einen Fehler über das Formular mit zusätzlichen Feldern.
Aus Gründen der Einfachheit, verwende ich einen Typ der Zweckmäßigkeit diese Funktionalität zu kapseln - siehe unten für das und ein Beispiel:
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";
}
}
Beispiel: Namespace Acme \ VariedCollectionBundle \ Formular;
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";
}
}
Haben Sie eine Idee, wie Sie Symfony2 dazu bringen können? Insbesondere "$ child-> getConfig() -> getOptions();" ist in 2.0 nicht verfügbar und daher kann ich die ursprünglichen Optionen für das Formular nicht abrufen. Wenn ich die Optionen auslasse, bekomme ich schließlich "Weder Eigenschaft 0" noch Methode "get0()" oder Methode "is0()" existiert in der Klasse "Doctrine \ ORM \ PersistentCollection" – CriticalImpact
@CriticalImpact Ich habe einen Blick durch die Quelle für die 2.0 Form-Komponente, und ich kann keine Möglichkeit sehen, den gleichen Effekt wirklich zu erreichen (die Optionen sind nicht auf lange Sicht in diesem gespeichert.) Sie können vielleicht tun, obwohl wenn Sie damit leben können Immer die Standardoptionen verwenden - um den Fehler, den Sie oben bekommen, zu lösen, sollten Sie nur property_path entsprechend setzen (leider muss ich Ihnen überlassen, um herauszufinden, welches Format 2.0 für Eigenschaftspfade in Sammlungen verwendet - a ein paar gut platzierte var_dumps sollten den Trick aber tuen) –
Ich schaffte es tatsächlich, eine andere Lösung zu finden: Ich habe einen Event-Listener für FormEvents :: PRE_SET_DATA hinzugefügt, das Backing-Objekt (in meinem Fall ein Frageobjekt) bekommen, den Typ bestimmt die Frage (Ich habe etwas in meinem que gesetzt Das hängt davon ab, ob es ein Kontrollkästchen ist (ja/nein, Textfeld usw.), und fügt dann das Feld dem Formular hinzu, das auf dem Typ basiert, der im Frageobjekt festgelegt ist. – CriticalImpact