10000 Merge branch 'dynamic_forms_after_PR8827' of github.com:Burgov/symfon… · symfony/symfony-docs@ade0d4b · GitHub
[go: up one dir, main page]

Skip to content

Commit ade0d4b

Browse files
committed
Merge branch 'dynamic_forms_after_PR8827' of github.com:Burgov/symfony-docs into Burgov-dynamic_forms_after_PR8827
Conflicts: cookbook/form/dynamic_form_modification.rst
2 parents aa0a511 + 862df0d commit ade0d4b

File tree

1 file changed

+51
-156
lines changed

1 file changed

+51
-156
lines changed

cookbook/form/dynamic_form_modification.rst

Lines changed: 51 additions & 156 deletions
Original file line numberDiff line numberDiff line change
@@ -403,37 +403,36 @@ possible choices will depend on each sport. Football will have attack, defense,
403403
goalkeeper etc... Baseball will have a pitcher but will not have goalkeeper. You
404404
will need the correct options to be set in order for validation to pass.
405405

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
407407
sport 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

552516
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.
624519

625520
One piece that may still be missing is the client-side updating of your form
626521
after the sport is selected. This should be handled by making an AJAX call

0 commit comments

Comments
 (0)
0