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