@@ -403,37 +403,36 @@ possible choices will depend on each sport. Football will have attack, defense,
403403goalkeeper etc... Baseball will have a pitcher but will not have goalkeeper. You
404404will need the correct options to be set in order for validation to pass.
405405
406- The meetup is passed as an entityhidden field to the form. So we can access each
406+ The meetup is passed as an entity field to the form. So we can access each
407407sport like this::
408408
409409 // src/Acme/DemoBundle/Form/Type/SportMeetupType.php
410410 namespace Acme\DemoBundle\Form\Type;
411-
411+
412412 use Symfony\Component\Form\FormBuilderInterface;
413413 use Symfony\Component\Form\FormEvent;
414414 use Symfony\Component\Form\FormEvents;
415-
415+ // ...
416+
416417 class SportMeetupType extends AbstractType
417418 {
418419 public function buildForm(FormBuilderInterface $builder, array $options)
419420 {
420421 $builder
421- ->add('number_of_people', 'text')
422- ->add('discount_coupon', 'text')
422+ ->add('sport', 'entity', array(...))
423423 ;
424- $factory = $builder->getFormFactory();
425424
426425 $builder->addEventListener(
427426 FormEvents::PRE_SET_DATA,
428- function(FormEvent $event)use ($factory) {
427+ function(FormEvent $event) {
429428 $form = $event->getForm();
430429
431430 // this would be your entity, i.e. SportMeetup
432431 $data = $event->getData();
433432
434433 $positions = $data->getSport()->getAvailablePositions();
435434
436- // ... proceed with customizing the form based on available positions
435+ $ form->add('position', 'entity', array('choices' => $ positions));
437436 }
438437 );
439438 }
@@ -454,173 +453,69 @@ On a form, we can usually listen to the following events:
454453* ``BIND ``
455454* ``POST_BIND ``
456455
457- When listening to ``BIND `` and ``POST_BIND ``, it's already "too late" to make
458- changes to the form. Fortunately, ``PRE_BIND `` is perfect for this. There
459- is, however, a big difference in what ``$event->getData() `` returns for each
460- of these events. Specifically, in ``PRE_BIND ``, ``$event->getData() `` returns
461- the raw data submitted by the user.
456+ ..versionadded ::2.2.6
462457
463- This can be used to get the ``SportMeetup `` id and retrieve it from the database,
464- given you have a reference to the object manager (if using doctrine). In
465- the end, you have an event subscriber that listens to two different events,
466- requires some external services and customizes the form. In such a situation,
467- it's probably better to define this as a service rather than using an anonymous
468- function as the event listener callback.
469458
470- The subscriber would now look like::
459+ The key is to add a ``POST_BIND `` listener to the field your new field is dependent
460+ on. If you add a POST_BIND listener to a form child, and add new children to the parent
461+ from there, the Form component will detect the new field automatically and maps it
462+ to the client data if it is available.
471463
472- // src/Acme/DemoBundle/Form/EventListener/RegistrationSportListener.php
473- namespace Acme\DemoBundle\Form\EventListener;
464+ The type would now look like::
474465
475- use Symfony\Component\Form\FormFactoryInterface;
476- use Doctrine\ORM\EntityManager;
477- use Symfony\Component\Form\FormEvent;
478- use Symfony\Component\Form\FormEvents;
479- use Symfony\Component\EventDispatcher\EventSubscriberInterface;
466+ // src/Acme/DemoBundle/Form/Type/SportMeetupType.php
467+ namespace Acme\DemoBundle\Form\Type;
480468
481- class RegistrationSportListener implements EventSubscriberInterface
482- {
483- /**
484- * @var FormFactoryInterface
485- */
486- private $factory;
487-
488- /**
489- * @var EntityManager
490- */
491- private $em;
492-
493- /**
494- * @param factory FormFactoryInterface
495- */
496- public function __construct(FormFactoryInterface $factory, EntityManager $em)
497- {
498- $this->factory = $factory;
499- $this->em = $em;
500- }
469+ // ...
470+ Acme\DemoBundle\Entity\Sport;
471+ Symfony\Component\Form\FormInterface;
501472
502- public static function getSubscribedEvents()
473+ class SportMeetupType extends AbstractType
474+ {
475+ public function buildForm(FormBuilderInterface $builder, array $options)
503476 {
504- return array(
505- FormEvents::PRE_BIND => 'preBind',
506- FormEvents::PRE_SET_DATA => 'preSetData',
507- );
508- }
477+ $builder
478+ ->add('sport', 'entity', array(...))
479+ ;
509480
510- /**
511- * @param event FormEvent
512- */
513- public function preSetData(FormEvent $event)
514- {
515- $meetup = $event->getData()->getMeetup();
481+ $formModifier = function(FormInterface $form, Sport $sport) {
482+ $positions = $data->getSport()->getAvailablePositions();
516483
517- // Before binding the form, the "meetup" will be null
518- if (null === $meetup) {
519- return;
484+ $form->add('position', 'entity', array('choices' => $positions));
520485 }
521486
522- $form = $event->getForm();
523- $positions = $meetup->getSport()->getPositions();
487+ $builder->addEventListener(
488+ FormEvents::PRE_SET_DATA,
489+ function(FormEvent $event) {
490+ $form = $event->getForm();
524491
525- $ this->customizeForm($form, $positions);
526- }
492+ // this would be your entity, i.e. SportMeetup
493+ $data = $event->getData();
527494
528- public function preBind(FormEvent $event)
529- {
530- $data = $event->getData();
531- $id = $data['event'];
532- $meetup = $this->em
533- ->getRepository('AcmeDemoBundle:SportMeetup')
534- ->find($id);
535-
536- if ($meetup === null) {
537- $msg = 'The event %s could not be found for your registration';
538- throw new \Exception(sprintf($msg, $id));
539- }
540- $form = $event->getForm();
541- $positions = $meetup->getSport()->getPositions();
495+ $formModifier($event->getForm(), $sport);
496+ }
497+ );
542498
543- $this->customizeForm($form, $positions);
544- }
499+ $builder->get('meetup')->addEventListener(
500+ FormEvents::POST_BIND,
501+ function(FormEvent $event) use ($formModifier) {
502+ // It's important here to fetch $event->getForm()->getData(), as
503+ // $event->getData() will get you the client data (this is, the ID)
504+ $sport = $event->getForm()->getData();
545505
546- protected function customizeForm($form, $positions)
547- {
548- // ... customize the form according to the positions
506+ $positions = $sport->getAvailablePositions();
507+
508+ // since we've added the listener to the child, we'll have to pass on
509+ // the parent to the callback functions!
510+ $formModifier($event->getForm()->getParent(), $sport);
511+ }
512+ );
549513 }
550514 }
551515
552516You can see that you need to listen on these two events and have different callbacks
553- only because in two different scenarios, the data that you can use is given in a
554- different format. Other than that, this class always performs exactly the same
555- things on a given form.
556-
557- Now that you have that setup, register your form and the listener as services:
558-
559- ..configuration-block ::
560-
561- ..code-block ::yaml
562-
563- # app/config/config.yml
564- acme.form.sport_meetup :
565- class :Acme\SportBundle\Form\Type\SportMeetupType
566- arguments :[@acme.form.meetup_registration_listener]
567- tags :
568- -{ name: form.type, alias: acme_meetup_registration }
569- acme.form.meetup_registration_listener
570- class :Acme\SportBundle\Form\EventListener\RegistrationSportListener
571- arguments :[@form.factory, @doctrine.orm.entity_manager]
572-
573- ..code-block ::xml
574-
575- <!-- app/config/config.xml-->
576- <services >
577- <service id =" acme.form.sport_meetup" class =" Acme\SportBundle\FormType\SportMeetupType" >
578- <argument type =" service" id =" acme.form.meetup_registration_listener" />
579- <tag name =" form.type" alias =" acme_meetup_registration" />
580- </service >
581- <service id =" acme.form.meetup_registration_listener" class =" Acme\SportBundle\Form\EventListener\RegistrationSportListener" >
582- <argument type =" service" id =" form.factory" />
583- <argument type =" service" id =" doctrine.orm.entity_manager" />
584- </service >
585- </services >
586-
587- ..code-block ::php
588-
589- // app/config/config.php
590- $definition = new Definition('Acme\SportBundle\Form\Type\SportMeetupType');
591- $definition->addTag('form.type', array('alias' => 'acme_meetup_registration'));
592- $container->setDefinition(
593- 'acme.form.meetup_registration_listener',
594- $definition,
595- array('security.context')
596- );
597- $definition = new Definition('Acme\SportBundle\Form\EventListener\RegistrationSportListener');
598- $container->setDefinition(
599- 'acme.form.meetup_registration_listener',
600- $definition,
601- array('form.factory', 'doctrine.orm.entity_manager')
602- );
603-
604- In this setup, the ``RegistrationSportListener `` will be a constructor argument
605- to ``SportMeetupType ``. You can then register it as an event subscriber on
606- your form::
607-
608- private $registrationSportListener;
609-
610- public function __construct(RegistrationSportListener $registrationSportListener)
611- {
612- $this->registrationSportListener = $registrationSportListener;
613- }
614-
615- public function buildForm(FormBuilderInterface $builder, array $options)
616- {
617- // ...
618- $builder->addEventSubscriber($this->registrationSportListener);
619- }
620-
621- And this should tie everything together. You can now retrieve your form from the
622- controller, display it to a user, and validate it with the right choice options
623- set for every possible kind of sport that our users are registering for.
517+ only because in two different scenarios, the data that you can use is available in different events.
518+ Other than that, the listeners always perform exactly the same things on a given form.
624519
625520One piece that may still be missing is the client-side updating of your form
626521after the sport is selected. This should be handled by making an AJAX call