Symfony2: добавление коллекции на основе структуры наследования таблиц в FormView

Я работаю над приложением Symfony2/Doctrine, которое использует наследование класса-таблицы (http://docs.doctrine-project.org/en/2.0.x/reference/inheritance-mapping.html#class-table- наследование) для управления Жалобами в Консультации. Каждый консультант может иметь много жалоб (OneToMany), и каждый тип жалобы имеет другую структуру и внешний вид. Жалобы - это коллекция и динамически добавляются в JS.

На этом этапе я могу продолжать жалобы и связывать их с Консулами, переработав их как соответствующие типы в контроллере до настойчивости. Я столкнулся с некоторыми проблемами с этим, и я планирую перенести это на событие формы (http://symfony.com/doc/current/cookbook/form/dynamic_form_generation.html) или что-то в этом роде для оптимизации процесса.

Однако проблема, с которой я столкнулась в данный момент, заключается в том, что я не могу отображать существующие жалобы в представлении с помощью FormView, потому что разработчик форм требует, чтобы я задал тип коллекции, которая будет отображаться. Если у каждого Консультата был только один тип Жалобы, это было бы хорошо, но они могли бы иметь несколько типов, а установка типа в построителе форм ограничивает меня одним типом.

Есть ли какой-то подход, который я могу предпринять, чтобы остановить FormView от tyring, чтобы преобразовать в строку в отсутствие какого-либо типа или какой-либо способ динамически определить и назначить тип на основе каждого жалоба (используя $complaints- > getComplaintType(), возможно)?

<?php
namespace Acme\ConsultBundle\Entity;
class Consult
{
    /**
     * @ORM\OneToMany(targetEntity="Acme\ConsultBundle\Entity\ComplaintBase", mappedBy="consult", cascade={"persist", "remove"})
     */
    protected $complaints;
}
?>


<?php
namespace Acme\ConsultBundle\Entity;
/**
 * Acme\ConsultBundle\Entity\ConsultBase
 *
 * @ORM\Entity
 * @ORM\Table(name="ConsultComplaintBase")
 * @ORM\HasLifecycleCallbacks
 * @ORM\InheritanceType("JOINED")
 * @ORM\DiscriminatorColumn(name="complaint_name", type="string")
 * @ORM\DiscriminatorMap({
 *  "ComplaintDefault"      = "Acme\ConsultBundle\Entity\ComplaintDefault",
 *  "ComplaintRosacea"      = "Acme\ConsultBundle\Entity\ComplaintRosacea",
 *  "ComplaintBotox"        = "Acme\ConsultBundle\Entity\ComplaintBotox",
 *  "ComplaintAcne"         = "Acme\ConsultBundle\Entity\ComplaintAcne",
 *  "ComplaintUrticaria"    = "Acme\ConsultBundle\Entity\ComplaintUrticaria",
 * })
 */
abstract class ComplaintBase
{
    /**
     * @ORM\ManyToOne(targetEntity="Acme\ConsultBundle\Entity\Consult", inversedBy="complaints")
     * @ORM\JoinColumn(name="consult_id", referencedColumnName="id")
     */
    protected $consult;
    /**
     * @ORM\Column(type="string", length="255")
     */
    protected $complaintType;
}
?>


<?php
namespace Acme\ConsultBundle\Form\Type;
class ConsultType extends AbstractType
{
    public function buildForm(FormBuilder $builder, array $options)
    {
        $builder
            ->add('complaints', 'collection', array(
                // 'type' => new ComplaintUrticariaType(),
                'error_bubbling' => true,
                'allow_add' => true,
                'allow_delete' => true,
                'by_reference' => false,
            ));
    }
}
?>

Ответ 1

Не совсем уверен, что он будет работать с коллекцией, но, безусловно, работает с одной формой. Попробуйте эту идею.

Сначала сделайте форму для вашей основной сущности ComplaintBase

class ComplaintForm extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $subscriber = new ComplaintSubscriber($builder);
        $builder->addEventSubscriber($subscriber);

        /* your fields */ 
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'Acme\ConsultBundle\Entity\ComplaintBase',
        ));
    }
}

Затем в подписчике вы можете определить дополнительные поля на основе указанного типа сущности.

class ComplaintSubscriber implements EventSubscriberInterface
{
    private $factory;
    private $builder;

    public function __construct(FormBuilderInterface $builder)
    {
        $this->factory = $builder->getFormFactory();
        $this->builder = $builder;
    }

    public static function getSubscribedEvents()
    {
        return array(
            FormEvents::PRE_SET_DATA => 'preSetData',
        );
    }

    public function preSetData(FormEvent $event)
    {
        $data = $event->getData();
        $form = $event->getForm();

        if (null === $data) {
            return;
        }

        $class = get_class($data);
        if( $class === 'Acme\ConsultBundle\Entity\ComplaintDefault' ) {
            $this->processDefault($data, $form);
        }
        elseif( $class === 'Acme\ConsultBundle\Entity\ComplaintRosacea' ) {
            $this->processRosacea($data, $form);
        }
        elseif( $class === 'Acme\ConsultBundle\Entity\ComplaintBotox' ) {
            $this->processBotox($data, $form);
        }
        else {
            #NOP
        }
    }

    protected function processDefault(Entity\ComplaintDefault $node, FormInterface &$form)
    {
        #NOP
    }

    protected function processRosacea(Entity\ComplaintRosacea $node, FormInterface &$form)
    {
        $form->add($this->factory->createNamed('some_field', 'text'));
    }

    protected function processBotox(Entity\ComplaintBotox $node, FormInterface &$form)
    {
        $form->add($this->factory->createNamed('other_field', 'text'));
    }
}