@@ -403,37 +403,36 @@ possible choices will depend on each sport. Football will have attack, defense,
403
403
goalkeeper etc... Baseball will have a pitcher but will not have goalkeeper. You
404
404
will need the correct options to be set in order for validation to pass.
405
405
406
- The meetup is passed as an entity hidden 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
407
407
sport like this::
408
408
409
409
// src/Acme/DemoBundle/Form/Type/SportMeetupType.php
410
410
namespace Acme\DemoBundle\Form\Type;
411
-
411
+
412
412
use Symfony\Component\Form\FormBuilderInterface;
413
413
use Symfony\Component\Form\FormEvent;
414
414
use Symfony\Component\Form\FormEvents;
415
-
415
+ // ...
416
+
416
417
class SportMeetupType extends AbstractType
417
418
{
418
419
public function buildForm(FormBuilderInterface $builder, array $options)
419
420
{
420
421
$builder
421
- ->add('number_of_people', 'text')
422
- ->add('discount_coupon', 'text')
422
+ ->add('sport', 'entity', array(...))
423
423
;
424
- $factory = $builder->getFormFactory();
425
424
426
425
$builder->addEventListener(
427
426
FormEvents::PRE_SET_DATA,
428
- function(FormEvent $event) use ($factory) {
427
+ function(FormEvent $event) {
429
428
$form = $event->getForm();
430
429
431
430
// this would be your entity, i.e. SportMeetup
432
431
$data = $event->getData();
433
432
434
433
$positions = $data->getSport()->getAvailablePositions();
435
434
436
- // ... proceed with customizing the form based on available positions
435
+ $ form->add('position', 'entity', array('choices' => $ positions));
437
436
}
438
437
);
439
438
}
@@ -454,173 +453,69 @@ On a form, we can usually listen to the following events:
454
453
* ``BIND ``
455
454
* ``POST_BIND ``
456
455
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
462
457
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.
469
458
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.
471
463
472
- // src/Acme/DemoBundle/Form/EventListener/RegistrationSportListener.php
473
- namespace Acme\DemoBundle\Form\EventListener;
464
+ The type would now look like::
474
465
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;
480
468
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;
501
472
502
- public static function getSubscribedEvents()
473
+ class SportMeetupType extends AbstractType
474
+ {
475
+ public function buildForm(FormBuilderInterface $builder, array $options)
503
476
{
504
- return array(
505
- FormEvents::PRE_BIND => 'preBind',
506
- FormEvents::PRE_SET_DATA => 'preSetData',
507
- );
508
- }
477
+ $builder
478
+ ->add('sport', 'entity', array(...))
479
+ ;
509
480
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();
516
483
517
- // Before binding the form, the "meetup" will be null
518
- if (null === $meetup) {
519
- return;
484
+ $form->add('position', 'entity', array('choices' => $positions));
520
485
}
521
486
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();
524
491
525
- $ this->customizeForm($form, $positions);
526
- }
492
+ // this would be your entity, i.e. SportMeetup
493
+ $data = $event->getData();
527
494
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
+ );
542
498
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();
545
505
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
+ );
549
513
}
550
514
}
551
515
552
516
You 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.
624
519
625
520
One piece that may still be missing is the client-side updating of your form
626
521
after the sport is selected. This should be handled by making an AJAX call
0 commit comments