Я привязываюсь к узлам, пытаясь бороться с создателями форм, событиями и трансформаторами 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()
прерывается, прежде чем вы сможете туда добраться.
Благодаря идеям из 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»)), вам не нужны трансформаторы.