@@ -270,11 +270,9 @@ and fill in the listener logic::
270
270
);
271
271
}
272
272
273
- $factory = $builder->getFormFactory();
274
-
275
273
$builder->addEventListener(
276
274
FormEvents::PRE_SET_DATA,
277
- function(FormEvent $event) use($user, $factory) {
275
+ function(FormEvent $event) use ($user) {
278
276
$form = $event->getForm();
279
277
280
278
$formOptions = array(
@@ -289,7 +287,7 @@ and fill in the listener logic::
289
287
290
288
// create the field, this is similar the $builder->add()
291
289
// field name, field type, data, options
292
- $form->add($factory->createNamed( 'friend', 'entity', null, $formOptions) );
290
+ $form->add('friend', 'entity', $formOptions);
293
291
}
294
292
);
295
293
}
@@ -372,16 +370,22 @@ it with :ref:`dic-tags-form-type`.
372
370
If you wish to create it from within a controller or any other service that has
373
371
access to the form factory, you then use::
374
372
375
- class FriendMessageController extends Controller
373
+ use Symfony\Component\DependencyInjection\ContainerAware;
374
+
375
+ class FriendMessageController extends ContainerAware
376
376
{
377
377
public function newAction(Request $request)
378
378
{
379
- $form = $this->createForm ('acme_friend_message');
379
+ $form = $this->get('form.factory')->create ('acme_friend_message');
380
380
381
381
// ...
382
382
}
383
383
}
384
384
385
+ If you extend the ``Symfony\Bundle\FrameworkBundle\Controller\Controller `` class, you can simply call::
386
+
387
+ $form = $this->createForm('acme_friend_message');
388
+
385
389
You can also easily embed the form type into another form::
386
390
387
391
// inside some other "form type" class
@@ -400,40 +404,39 @@ the data that was submitted by the user. For example, imagine you have a registr
400
404
form for sports gatherings. Some events will allow you to specify your preferred
401
405
position on the field. This would be a ``choice `` field for example. However the
402
406
possible choices will depend on each sport. Football will have attack, defense,
403
- goalkeeper etc... Baseball will have a pitcher but will not have goalkeeper. You
404
- will need the correct options to be set in order for validation to pass.
407
+ goalkeeper etc... Baseball will have a pitcher but will not have a goalkeeper. You
408
+ will need the correct options in order for validation to pass.
405
409
406
- The meetup is passed as an entity hidden field to the form. So we can access each
410
+ The meetup is passed as an entity field to the form. So we can access each
407
411
sport like this::
408
412
409
413
// src/Acme/DemoBundle/Form/Type/SportMeetupType.php
410
414
namespace Acme\DemoBundle\Form\Type;
411
-
415
+
412
416
use Symfony\Component\Form\FormBuilderInterface;
413
417
use Symfony\Component\Form\FormEvent;
414
418
use Symfony\Component\Form\FormEvents;
415
-
419
+ // ...
420
+
416
421
class SportMeetupType extends AbstractType
417
422
{
418
423
public function buildForm(FormBuilderInterface $builder, array $options)
419
424
{
420
425
$builder
421
- ->add('number_of_people', 'text')
422
- ->add('discount_coupon', 'text')
426
+ ->add('sport', 'entity', array(...))
423
427
;
424
- $factory = $builder->getFormFactory();
425
428
426
429
$builder->addEventListener(
427
430
FormEvents::PRE_SET_DATA,
428
- function(FormEvent $event) use ($factory) {
431
+ function(FormEvent $event) {
429
432
$form = $event->getForm();
430
433
431
434
// this would be your entity, i.e. SportMeetup
432
435
$data = $event->getData();
433
436
434
437
$positions = $data->getSport()->getAvailablePositions();
435
438
436
- // ... proceed with customizing the form based on available positions
439
+ $ form->add('position', 'entity', array('choices' => $ positions));
437
440
}
438
441
);
439
442
}
@@ -458,173 +461,71 @@ On a form, we can usually listen to the following events:
458
461
The events ``PRE_SUBMIT ``, ``SUBMIT `` and ``POST_SUBMIT `` were added in
459
462
Symfony 2.3. Before, they were named ``PRE_BIND ``, ``BIND `` and ``POST_BIND ``.
460
463
461
- When listening to ``SUBMIT `` and ``POST_SUBMIT ``, it's already "too late" to make
462
- changes to the form. Fortunately, ``PRE_SUBMIT `` is perfect for this. There
463
- is, however, a big difference in what ``$event->getData() `` returns for each
464
- of these events. Specifically, in ``PRE_SUBMIT ``, ``$event->getData() `` returns
465
- the raw data submitted by the user.
464
+ .. versionadded :: 2.2.6
466
465
467
- This can be used to get the ``SportMeetup `` id and retrieve it from the database,
468
- given you have a reference to the object manager (if using doctrine). In
469
- the end, you have an event subscriber that listens to two different events,
470
- requires some external services and customizes the form. In such a situation,
471
- it's probably better to define this as a service rather than using an anonymous
472
- function as the event listener callback.
466
+ The behavior of the ``POST_SUBMIT `` changed slightly in 2.2.6, which the
467
+ below example uses.
473
468
474
- The subscriber would now look like::
469
+ The key is to add a ``POST_SUBMIT `` listener to the field that your new field
470
+ depends on. If you add a ``POST_SUBMIT `` listener to a form child (e.g. ``sport`),
471
+ and add new children to the parent form, the Form component will detect the
472
+ new field automatically and map it to the submitted client data.
475
473
476
- // src/Acme/DemoBundle/Form/EventListener/RegistrationSportListener.php
477
- namespace Acme\DemoBundle\Form\EventListener;
474
+ The type would now look like::
478
475
479
- use Symfony\Component\Form\FormFactoryInterface;
480
- use Doctrine\ORM\EntityManager;
481
- use Symfony\Component\Form\FormEvent;
482
- use Symfony\Component\Form\FormEvents;
483
- use Symfony\Component\EventDispatcher\EventSubscriberInterface;
476
+ // src/Acme/DemoBundle/Form/Type/SportMeetupType.php
477
+ namespace Acme\DemoBundle\Form\Type;
484
478
485
- class RegistrationSportListener implements EventSubscriberInterface
486
- {
487
- /**
488
- * @var FormFactoryInterface
489
- */
490
- private $factory;
491
-
492
- /**
493
- * @var EntityManager
494
- */
495
- private $em;
496
-
497
- /**
498
- * @param factory FormFactoryInterface
499
- */
500
- public function __construct(FormFactoryInterface $factory, EntityManager $em)
501
- {
502
- $this->factory = $factory;
503
- $this->em = $em;
504
- }
479
+ // ...
480
+ Acme\DemoBundle\Entity\Sport;
481
+ Symfony\Component\Form\FormInterface;
505
482
506
- public static function getSubscribedEvents()
483
+ class SportMeetupType extends AbstractType
484
+ {
485
+ public function buildForm(FormBuilderInterface $builder, array $options)
507
486
{
508
- return array(
509
- FormEvents::PRE_SUBMIT => 'preSubmit',
510
- FormEvents::PRE_SET_DATA => 'preSetData',
511
- );
512
- }
487
+ $builder
488
+ ->add('sport', 'entity', array(...))
489
+ ;
513
490
514
- /**
515
- * @param event FormEvent
516
- */
517
- public function preSetData(FormEvent $event)
518
- {
519
- $meetup = $event->getData()->getMeetup();
491
+ $formModifier = function(FormInterface $form, Sport $sport) {
492
+ $positions = $data->getSport()->getAvailablePositions();
520
493
521
- // Before SUBMITing the form, the "meetup" will be null
522
- if (null === $meetup) {
523
- return;
494
+ $form->add('position', 'entity', array('choices' => $positions));
524
495
}
525
496
526
- $form = $event->getForm();
527
- $positions = $meetup->getSport()->getPositions();
497
+ $builder->addEventListener(
498
+ FormEvents::PRE_SET_DATA,
499
+ function(FormEvent $event) {
500
+ $form = $event->getForm();
528
501
529
- $ this->customizeForm($form, $positions);
530
- }
502
+ // this would be your entity, i.e. SportMeetup
503
+ $data = $event->getData();
531
504
532
- public function preSubmit(FormEvent $event)
533
- {
534
- $data = $event->getData();
535
- $id = $data['event'];
536
- $meetup = $this->em
537
- ->getRepository('AcmeDemoBundle:SportMeetup')
538
- ->find($id);
539
-
540
- if ($meetup === null) {
541
- $msg = 'The event %s could not be found for your registration';
542
- throw new \Exception(sprintf($msg, $id));
543
- }
544
- $form = $event->getForm();
545
- $positions = $meetup->getSport()->getPositions();
505
+ $formModifier($event->getForm(), $sport);
506
+ }
507
+ );
546
508
547
- $this->customizeForm($form, $positions);
548
- }
509
+ $builder->get('sport')->addEventListener(
510
+ FormEvents::POST_SUBMIT,
511
+ function(FormEvent $event) use ($formModifier) {
512
+ // It's important here to fetch $event->getForm()->getData(), as
513
+ // $event->getData() will get you the client data (this is, the ID)
514
+ $sport = $event->getForm()->getData();
549
515
550
- protected function customizeForm($form, $positions)
551
- {
552
- // ... customize the form according to the positions
516
+ $positions = $sport->getAvailablePositions();
517
+
518
+ // since we've added the listener to the child, we'll have to pass on
519
+ // the parent to the callback functions!
520
+ $formModifier($event->getForm()->getParent(), $sport);
521
+ }
522
+ );
553
523
}
554
524
}
555
525
556
526
You can see that you need to listen on these two events and have different callbacks
557
- only because in two different scenarios, the data that you can use is given in a
558
- different format. Other than that, this class always performs exactly the same
559
- things on a given form.
560
-
561
- Now that you have that setup, register your form and the listener as services:
562
-
563
- .. configuration-block ::
564
-
565
- .. code-block :: yaml
566
-
567
- # app/config/config.yml
568
- acme.form.sport_meetup :
569
- class : Acme\SportBundle\Form\Type\SportMeetupType
570
- arguments : [@acme.form.meetup_registration_listener]
571
- tags :
572
- - { name: form.type, alias: acme_meetup_registration }
573
- acme.form.meetup_registration_listener
574
- class : Acme\SportBundle\Form\EventListener\RegistrationSportListener
575
- arguments : [@form.factory, @doctrine.orm.entity_manager]
576
-
577
- .. code-block :: xml
578
-
579
- <!-- app/config/config.xml -->
580
- <services >
581
- <service id =" acme.form.sport_meetup" class =" Acme\SportBundle\FormType\SportMeetupType" >
582
- <argument type =" service" id =" acme.form.meetup_registration_listener" />
583
- <tag name =" form.type" alias =" acme_meetup_registration" />
584
- </service >
585
- <service id =" acme.form.meetup_registration_listener" class =" Acme\SportBundle\Form\EventListener\RegistrationSportListener" >
586
- <argument type =" service" id =" form.factory" />
587
- <argument type =" service" id =" doctrine.orm.entity_manager" />
588
- </service >
589
- </services >
590
-
591
- .. code-block :: php
592
-
593
- // app/config/config.php
594
- $definition = new Definition('Acme\SportBundle\Form\Type\SportMeetupType');
595
- $definition->addTag('form.type', array('alias' => 'acme_meetup_registration'));
596
- $container->setDefinition(
597
- 'acme.form.meetup_registration_listener',
598
- $definition,
599
- array('security.context')
600
- );
601
- $definition = new Definition('Acme\SportBundle\Form\EventListener\RegistrationSportListener');
602
- $container->setDefinition(
603
- 'acme.form.meetup_registration_listener',
604
- $definition,
605
- array('form.factory', 'doctrine.orm.entity_manager')
606
- );
607
-
608
- In this setup, the ``RegistrationSportListener `` will be a constructor argument
609
- to ``SportMeetupType ``. You can then register it as an event subscriber on
610
- your form::
611
-
612
- private $registrationSportListener;
613
-
614
- public function __construct(RegistrationSportListener $registrationSportListener)
615
- {
616
- $this->registrationSportListener = $registrationSportListener;
617
- }
618
-
619
- public function buildForm(FormBuilderInterface $builder, array $options)
620
- {
621
- // ...
622
- $builder->addEventSubscriber($this->registrationSportListener);
623
- }
624
-
625
- And this should tie everything together. You can now retrieve your form from the
626
- controller, display it to a user, and validate it with the right choice options
627
- set for every possible kind of sport that our users are registering for.
527
+ only because in two different scenarios, the data that you can use is available in different events.
528
+ Other than that, the listeners always perform exactly the same things on a given form.
628
529
629
530
One piece that may still be missing is the client-side updating of your form
630
531
after the sport is selected. This should be handled by making an AJAX call
0 commit comments