События формы Symfony2 и модельные трансформаторы

Я привязываюсь к узлам, пытаясь бороться с создателями форм, событиями и трансформаторами Symfony2 … надеюсь, кто-то здесь более опытен и может помочь!

У меня есть поле формы (выберите раскрывающийся список), который содержит некоторые значения (краткий список), который отображается в Entity. Один из этих вариантов – «другой». Предположим, что AJAX пока нет, и когда пользователь отправляет форму, которую я хочу определить, выбрали ли они «другое» (или любой другой вариант, не входящий в список). Если они выбрали один из этих вариантов, то должен быть показан полный список опций, иначе просто покажите краткий список. Должно быть легко, не так ли? 😉

Итак, у меня есть свой тип формы, и он отображает основной список как раз отлично. Код выглядит примерно так:

namespace Company\ProjectBundle\Form\Type; use ... class FancyFormType extends AbstractType { private $fooRepo; public function __construct(EntityManager $em, FooRepository $fooRepo) { $this->fooRepo = $fooRepo; } public function buildForm(FormBuilderInterface $builder, array $options) { /** @var Bar $bar */ $bar = $builder->getData(); $fooTransformer = new FooToStringTransformer($options['em']); $builder ->add($builder ->create('linkedFoo', 'choice', array( 'choices' => $this->fooRepo->getListAsArray( $bar->getLinkedfoo()->getId() ), )) ->addModelTransformer($fooTransformer) ) ; // ... } // ... } 

Теперь я хочу проверить представленное значение, поэтому я использую прослушиватель форм событий следующим образом.

 public function buildForm(FormBuilderInterface $builder, array $options) { // ... This code comes just after the snippet shown above $builder->addEventListener(FormEvents::PRE_SUBMIT, function(FormEvent $event) { /** @var EntityManager $em */ $em = $event->getForm()->getConfig()->getOption('em'); $data = $event->getData(); if (empty($data['linkedFoo'])) return; $selectedFoo = $data['linkedfoo']; $event->getForm()->add('linkedFoo', 'choice', array( 'choices' => $em ->getRepository('CompanyProjectBundle:FooShortlist') ->getListAsArray($selectedFoo) , )); //@todo - needs transformer? }); } 

Однако он не работает с сообщением об ошибке:

 Notice: Object of class Proxies\__CG__\Company\ProjectBundle\Entity\Foo could not be converted to int in \path\to\project\symfony\symfony\src\Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceList.php line 458 

Я предполагаю, что эта ошибка linkedFoo тем, что когда linkedFoo был переписан, он удалил modelTransformer ? Я пробовал различные способы доступа к строителю при закрытии события, но это, похоже, не работало (возвращаемые значения были неожиданными). Есть ли другой метод, который я должен использовать в событии, отличном от $event->getForm()->add() ? Или есть более фундаментальная проблема с моим подходом здесь?

В принципе, я не хочу linkedFoo в конфигурацию / трансформаторы / метки полей linkedFoo , кроме как изменить доступные варианты … есть ли другой способ сделать это? Например что-то вроде $form->getField()->updateChoices() ?

Заранее благодарим за любую помощь, которую вы можете предложить!

С

PS есть ли какая-нибудь лучшая документация или обсуждение форм, событий и т. Д., Чем на веб-сайте Symfony? Например, какая разница между PRE_SET_DATA, PRE_SUBMIT, SUBMIT и т. Д.? Когда они увольняются? Для чего они должны использоваться? Как наследование работает с полями пользовательских форм? Что такое форма и строитель, как они взаимодействуют, и когда вы должны иметь дело с каждым? Как, когда и почему вы должны использовать FormFactory, вы можете получить доступ через $form->getConfig()->getFormFactory() ? И т.д..


Редактировать: В ответ на предложение Флориана вот еще информация о вещах, которые были опробованы, но не работают:

Если вы попытаетесь получить FormBuilder внутри события следующим образом:

 /** @var FormBuilder $builder */ $builder = $event->getForm()->get('linkedFoo')->getConfig(); $event->getForm()->add($builder ->create('linkedFoo', 'choice', array( 'choices' => $newChoices, 'label' =>'label', )) ->addModelTransformer(new FooToStringTransformer($em)) ); 

Затем вы получите сообщение об ошибке:

 FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance. 

Итак, вы попробуете что-то вроде Флориана,

 $event->getForm()->add('linkedFoo', 'choice', array( 'choices' => $newChoices, )); $event->getForm()->get('linkedFoo')->getConfig()->addModelTransformer(new FooToStringTransformer($em)); 

… но вместо этого вы получите эту ошибку:

 Notice: Object of class Proxies\__CG__\Company\ProjectBundle\Entity\Foo could not be converted to int in C:\path\to\vendor\symfony\symfony\src\Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceList.php line 458 

Похоже, что вторая строка (которая добавляет ModelTransformer) никогда не вызывается, потому что вызов ->add() прерывается, прежде чем вы сможете туда добраться.

Solutions Collecting From Web of "События формы Symfony2 и модельные трансформаторы"

Благодаря идеям из sstok (на github), я думаю, что теперь у меня это работает. Ключом является создание настраиваемого типа формы, а затем его использование для добавления ModelTransformer.

Создайте собственный тип формы:

 namespace Caponica\MagnetBundle\Form\Type; use ... class FooShortlistChoiceType extends AbstractType { protected $em; public function __construct(EntityManager $entityManager) { $this->em = $entityManager; } public function buildForm(FormBuilderInterface $builder, array $options) { $fooTransformer = new FooToStringTransformer($this->em); $builder ->addModelTransformer($fooTransformer) ; } public function getParent() { return 'choice'; } public function getName() { return 'fooShortlist'; } } 

Создайте определение сервиса для нового типа:

 company_project.form.type.foo_shortlist: class: Company\ProjectBundle\Form\Type\FooShortlistChoiceType tags: - { name: form.type, alias: fooShortlist } arguments: - @doctrine.orm.entity_manager 

Код главной формы теперь выглядит примерно так:

 namespace Company\ProjectBundle\Form\Type; use ... class FancyFormType extends AbstractType { private $fooRepo; public function __construct(FooRepository $fooRepo) { $this->fooRepo = $fooRepo; } public function buildForm(FormBuilderInterface $builder, array $options) { /** @var Bar $bar */ $bar = $builder->getData(); $fooTransformer = new FooToStringTransformer($options['em']); $builder ->add('linkedFoo', 'fooShortlist', array( 'choices' => $this->fooRepo->getListAsArray( $bar->getLinkedfoo()->getId() ), )) ; $builder->addEventListener(FormEvents::PRE_SUBMIT, function(FormEvent $event) { /** @var EntityManager $em */ $em = $event->getForm()->getConfig()->getOption('em'); $data = $event->getData(); if (empty($data['linkedFoo'])) return; $selectedFoo = $data['linkedFoo']; $event->getForm()->add('linkedFoo', 'fooShortlist', array( 'choices' => $em->getRepository('CaponicaMagnetBundle:FooShortlist')->getListAsArray($selectedFoo), 'label' => 'label' )); }); // ... } // ... } 

Ключ в том, что этот метод позволяет встраивать ModelTransformer в пользовательский тип поля, так что всякий раз, когда вы добавляете новый экземпляр этого типа, он автоматически добавляет ModelTransformer для вас и предотвращает предыдущий цикл «не может добавить поле без трансформатор И не может добавить трансформатор без поля "

Ваш слушатель выглядит (почти :)) нормально.

Просто используйте PRE_SUBMIT. В этом случае $event->getData() будет представлять собой необработанные данные формы (массив), которые отправляются. $selectedFoo будет potentailly содержать «другое».

Если это так, вы замените поле «короткий» «выбор» полным, используя formFactory в слушателе.

 $builder->addEventListener(FormEvents::PRE_SUBMIT, function(FormEvent $event) { $data = $event->getData(); if (empty($data['linkedFoo']) || $data['linkedFoo'] !== 'other') { return; } // now we know user choose "other" // so we'll change the "linkedFoo" field with a "fulllist" $event->getForm()->add('linkedFoo', 'choice', array( 'choices' => $fullList, // $em->getRepository('Foo')->getFullList() ? )); $event->getForm()->get('linkedFoo')->getConfig()->addModelTransformer(new FooTransformer); }); 

Вы задали столько вопросов, я не знаю, с чего начать.

Что касается dataTransformers: пока вы не захотите преобразовать необработанные данные в другое представление («2013-01-01» -> новое DateTime («2013-01-01»)), вам не нужны трансформаторы.