I'm getting tied in knots trying to wrestle with Symfony2's form builders, events and transformers... hopefully somebody here is more experienced and can help out!
I have a form field (select drop-down) which contains some values (a shortlist) which maps to an Entity. One of these options is "other". Assume there's no AJAX for now and when a user submits the form I want to detect if they chose 'other' (or any other option not in the shortlist). If they chose one of these options then the full list of options should be shown, otherwise just show the shortlist. Should be easy, right? ;)
So, I have my Form Type and it displays the basic shortlist just fine. The code looks something like this:
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) ) ; // ... } // ... }
Now, I want to check the value submitted so I use a Form Event Listener as follows.
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? }); }
However, it fails with an error message like this:
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
I presume this error is because when the linkedFoo
was over-written it removed the modelTransformer
? I tried various ways of accessing a builder in the event's closure but this didn't seem to work (the return values were unexpected). Is there some other method I should be using in the event other than $event->getForm()->add()
? Or is there a more fundamental problem with my approach here?
Basically I don't want to mess with the linkedFoo
field's config/transformers/labels except to change the choices available... is there some other way to do that? E.g. something like $form->getField()->updateChoices()
?
Thanks in advance for any help you can offer!
C
P.S. is there any better documentation or discussion of the forms, events, etc than on the Symfony website? E.g. what's the difference between PRE_SET_DATA, PRE_SUBMIT, SUBMIT, etc? When are they fired? What should they be used for? How does inheritance work with custom form fields? What is a Form and a Builder, how do they interact and when should you deal with each? How, when and why should you use the FormFactory you can access through $form->getConfig()->getFormFactory()
? Etc..
Edit: In response to Florian's suggestion here's some more info about things that were tried but do not work:
If you try to get the FormBuilder within the event like this:
/** @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)) );
Then you get the error:
FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.
So then you try something like Florian suggested, i.e.
$event->getForm()->add('linkedFoo', 'choice', array( 'choices' => $newChoices, )); $event->getForm()->get('linkedFoo')->getConfig()->addModelTransformer(new FooToStringTransformer($em));
...but you get this error instead:
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
Which seems to suggest that the second line (which adds the ModelTransformer) is never called because the ->add()
call is failing before you can get there.