Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Refactor: Dynamic forms cookbook entry #2417

Merged
merged 2 commits into from

2 participants

Ryan Weaver Wouter J
Ryan Weaver
Collaborator
Q A
Doc fix? yes
New docs? no
Applies to or 2.1+

The dynamic forms cookbook entry was enhanced greatly in #1842. However, it lacks some consistency now, along with a number of other issues. I've done my best here as a first pass at fixing all of this and getting this entry up to the level where it needs to be.

My goal here is to:

1) Get any more obvious issues fixed and merge this in
2) After, create an issue so we can track the remaining things that need to be updated. These include:

  • Simplify the first example - a subscriber is overkill, a listener is easier, and a callback is probably the best approach

  • We need to document every form event and its purpose, what its data is, etc

  • Both the FriendMessage and SportsMeetup examples aren't really completed. They leave out more detail than I like and so aren't copy-and-pastable. This is especially true of SportsMeetup, where I ran out of steam to keep going :). Ideally, all three examples would be more consistent - building up on the same example, not three different examples.

  • I think we need to spend some time on the AJAX experience that's so common when you have one form field that depends on another (i.e. user selects "Country", AJAX request is made to update the "State" field).

Thanks!

cookbook/form/dynamic_form_modification.rst
((35 lines not shown))
-While this example could have used the ``form.post_set_data``
-event just as effectively, by using ``form.pre_set_data`` you guarantee that
-the data being retrieved from the ``Event`` object has in no way been modified
-by any other subscribers or listeners because ``form.pre_set_data`` is the
-first form event dispatched.
+ The ``FormEvents::PRE_SET_DATA`` line actually resolves to the string
+ ``form.pre_set_data``. :class:`Symfony\\Component\\Form\\FormEvents` serves an organizational purpose. It is a centralized location
Wouter J Collaborator
WouterJ added a note

some linebreaks missing here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Ryan Weaver weaverryan merged commit d9becd2 into from
Ryan Weaver weaverryan deleted the branch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Apr 4, 2013
  1. Ryan Weaver

    [#1842] Heavily reworking the dynamic for modification chapter - ther…

    weaverryan authored
    …e is still quite a bit left to do
  2. Ryan Weaver
This page is out of date. Refresh to see the latest.
2  cookbook/form/create_custom_field_type.rst
View
@@ -207,6 +207,8 @@ But this only works because the ``GenderType()`` is very simple. What if
the gender codes were stored in configuration or in a database? The next
section explains how more complex field types solve this problem.
+.. _form-cookbook-form-field-service:
+
Creating your Field Type as a Service
-------------------------------------
399 cookbook/form/dynamic_form_modification.rst
View
@@ -2,7 +2,32 @@
single: Form; Events
How to Dynamically Modify Forms Using Form Events
-===================================================
+=================================================
+
+Often times, a form can't be created statically. In this entry, you'll learn
+how to customize your form based on three common use-cases:
+
+1) :ref:`cookbook-form-events-underlying-data`
+
+Example: you have a "Product" form and need to modify/add/remove a field
+based on the data on the underlying Product being edited.
+
+2) :ref:`cookbook-form-events-user-data`
+
+Example: you create a "Friend Message" form and need to build a drop-down
+that contains only users that are friends with the *current* authenticated
+user.
+
+3) :ref:`cookbook-form-events-submitted-data`
+
+Example: on a registration form, you have a "country" field and a "state"
+field which should populate dynamically based on the value in the "country"
+field.
+
+.. _cookbook-form-events-underlying-data:
+
+Customizing your Form based on the underlying Data
+--------------------------------------------------
Before jumping right into dynamic form generation, let's have a quick review
of what a bare form class looks like::
@@ -12,6 +37,7 @@ of what a bare form class looks like::
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
+ use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class ProductType extends AbstractType
{
@@ -21,6 +47,13 @@ of what a bare form class looks like::
$builder->add('price');
}
+ public function setDefaultOptions(OptionsResolverInterface $resolver)
+ {
+ $resolver->setDefaults(array(
+ 'data_class' => 'Acme\DemoBundle\Entity\Product'
+ ));
+ }
+
public function getName()
{
return 'product';
@@ -33,9 +66,9 @@ of what a bare form class looks like::
probably need to take a step back and first review the :doc:`Forms chapter </book/forms>`
before proceeding.
-Let's assume for a moment that this form utilizes an imaginary "Product" class
-that has only two relevant properties ("name" and "price"). The form generated
-from this class will look the exact same regardless if a new Product is being created
+Assume for a moment that this form utilizes an imaginary "Product" class
+that has only two properties ("name" and "price"). The form generated from
+this class will look the exact same regardless if a new Product is being created
or if an existing product is being edited (e.g. a product fetched from the database).
Suppose now, that you don't want the user to be able to change the ``name`` value
@@ -48,7 +81,7 @@ flexibility to your forms.
.. _`cookbook-forms-event-subscriber`:
Adding An Event Subscriber To A Form Class
-------------------------------------------
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
So, instead of directly adding that "name" widget via your ProductType form
class, let's delegate the responsibility of creating that particular field
@@ -57,8 +90,7 @@ to an Event Subscriber::
// src/Acme/DemoBundle/Form/Type/ProductType.php
namespace Acme\DemoBundle\Form\Type;
- use Symfony\Component\Form\AbstractType;
- use Symfony\Component\Form\FormBuilderInterface;
+ // ...
use Acme\DemoBundle\Form\EventListener\AddNameFieldSubscriber;
class ProductType extends AbstractType
@@ -70,10 +102,7 @@ to an Event Subscriber::
$builder->add('price');
}
- public function getName()
- {
- return 'product';
- }
+ // ...
}
The event subscriber is passed the FormFactory object in its constructor so
@@ -83,7 +112,7 @@ notified of the dispatched event during form creation.
.. _`cookbook-forms-inside-subscriber-class`:
Inside the Event Subscriber Class
----------------------------------
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The goal is to create a "name" field *only* if the underlying Product object
is new (e.g. hasn't been persisted to the database). Based on that, the subscriber
@@ -118,62 +147,45 @@ might look like the following::
$data = $event->getData();
$form = $event->getForm();
- // During form creation setData() is called with null as an argument
- // by the FormBuilder constructor. You're only concerned with when
- // setData is called with an actual Entity object in it (whether new
- // or fetched with Doctrine). This if statement lets you skip right
- // over the null condition.
- if (null === $data) {
- return;
- }
-
// check if the product object is "new"
- if (!$data->getId()) {
+ // If you didn't pass any data to the form, the data is "null".
+ // This should be considered a new "Product"
+ if (!$data || !$data->getId()) {
$form->add($this->factory->createNamed('name', 'text'));
}
}
}
-.. caution::
-
- It is easy to misunderstand the purpose of the ``if (null === $data)`` segment
- of this event subscriber. To fully understand its role, you might consider
- also taking a look at the `Form class`_ and paying special attention to
- where setData() is called at the end of the constructor, as well as the
- setData() method itself.
-
-The ``FormEvents::PRE_SET_DATA`` line actually resolves to the string ``form.pre_set_data``.
-The `FormEvents class`_ serves an organizational purpose. It is a centralized location
-in which you can find all of the various form events available.
+.. tip::
-While this example could have used the ``form.post_set_data``
-event just as effectively, by using ``form.pre_set_data`` you guarantee that
-the data being retrieved from the ``Event`` object has in no way been modified
-by any other subscribers or listeners because ``form.pre_set_data`` is the
-first form event dispatched.
+ The ``FormEvents::PRE_SET_DATA`` line actually resolves to the string
+ ``form.pre_set_data``. :class:`Symfony\\Component\\Form\\FormEvents` serves
+ an organizational purpose. It is a centralized location in which you can
+ find all of the various form events available.
.. note::
- You may view the full list of form events via the `FormEvents class`_,
- found in the form bundle.
+ You can view the full list of form events via the :class:`Symfony\\Component\\Form\\FormEvents`
+ class.
-How to Dynamically Generate Forms based on user data
-====================================================
+.. _cookbook-form-events-user-data:
+
+How to Dynamically Generate Forms based on user Data
+----------------------------------------------------
Sometimes you want a form to be generated dynamically based not only on data
-from this form but also on something else. For example depending on the user
-currently using the application. If you have a social website where a user can
-only message people who are his friends on the website, then the current user
-doesn't need to be included as a field of your form, but a "choice list" of
-whom to message should only contain users that are the current user's friends.
+from the form but also on something else - like some data from the current user.
+Suppose you have a social website where a user can only message people who
+are his friends on the website. In this case, a "choice list" of whom to message
+should only contain users that are the current user's friends.
-Creating the form type
-----------------------
+Creating the Form Type
+~~~~~~~~~~~~~~~~~~~~~~
-Using an event listener, our form could be built like this::
+Using an event listener, your form might look like this::
- // src/Acme/DemoBundle/FormType/FriendMessageFormType.php
- namespace Acme\DemoBundle\FormType;
+ // src/Acme/DemoBundle/Form/Type/FriendMessageFormType.php
+ namespace Acme\DemoBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
@@ -181,7 +193,6 @@ Using an event listener, our form could be built like this::
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Security\Core\SecurityContext;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
- use Acme\DemoBundle\FormSubscriber\UserListener;
class FriendMessageFormType extends AbstractType
{
@@ -206,13 +217,11 @@ Using an event listener, our form could be built like this::
}
}
-The problem is now to get the current application user and create a choice field
-that would contain only this user's friends.
+The problem is now to get the current user and create a choice field that
+contains only this user's friends.
Luckily it is pretty easy to inject a service inside of the form. This can be
-done in the constructor.
-
-.. code-block:: php
+done in the constructor::
private $securityContext;
@@ -223,19 +232,26 @@ done in the constructor.
.. note::
- You might wonder, now that we have access to the User (through) the security
- context, why don't we just use that inside of the buildForm function and
- still use a listener?
- This is because doing so in the buildForm method would result in the whole
- form type being modified and not only one form instance.
+ You might wonder, now that you have access to the User (through the security
+ context), why not just use it directly in ``buildForm`` and omit the
+ event listener? This is because doing so in the ``buildForm`` method
+ would result in the whole form type being modified and not just this
+ one form instance. This may not usually be a problem, but technically
+ a single form type could be used on a single request to create many forms
+ or fields.
-Customizing the form type
--------------------------
+Customizing the Form Type
+~~~~~~~~~~~~~~~~~~~~~~~~~
-Now that we have all the basics in place, we can put everything in place and add
-our listener::
+Now that you have all the basics in place you an take advantage of the ``securityContext``
+and fill in the listener logic::
// src/Acme/DemoBundle/FormType/FriendMessageFormType.php
+
+ use Symfony\Component\Security\Core\SecurityContext;
+ use Doctrine\ORM\EntityRepository;
+ // ...
+
class FriendMessageFormType extends AbstractType
{
private $securityContext;
@@ -251,66 +267,98 @@ our listener::
->add('subject', 'text')
->add('body', 'textarea')
;
+
+ // grab the user, do a quick sanity check that one exists
$user = $this->securityContext->getToken()->getUser();
+ if (!$user) {
+ throw new \LogicException(
+ 'The FriendMessageFormType cannot be used without an authenticated user!'
+ );
+ }
+
$factory = $builder->getFormFactory();
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function(FormEvent $event) use($user, $factory){
$form = $event->getForm();
- $userId = $user->getId();
$formOptions = array(
- 'class' => 'Acme\DemoBundle\Document\User',
+ 'class' => 'Acme\DemoBundle\Entity\User',
'multiple' => false,
'expanded' => false,
'property' => 'fullName',
- 'query_builder' => function(DocumentRepository $dr) use ($userId) {
- return $dr->createQueryBuilder()->field('friends.$id')->equals(new \MongoId($userId));
+ 'query_builder' => function(EntityRepository $er) use ($user) {
+ // build a custom query, or call a method on your repository (even better!)
},
);
- $form->add($factory->createNamed('friend', 'document', null, $formOptions));
+ // create the field, this is similar the $builder->add()
+ // field name, field type, data, options
+ $form->add($factory->createNamed('friend', 'entity', null, $formOptions));
}
);
}
- public function getName()
- {
- return 'acme_friend_message';
- }
+ // ...
+ }
- public function setDefaultOptions(OptionsResolverInterface $resolver)
+Using the Form
+~~~~~~~~~~~~~~
+
+Our form is now ready to use and there are two possible ways to use it inside
+of a controller:
+
+a) create it manually and remember to pass the security context to it;
+
+or
+
+b) define it as a service.
+
+a) Creating the Form manually
+.............................
+
+This is very simple, and is probably the better approach unless you're using
+your new form type in many places or embedding it into other forms::
+
+ class FriendMessageController extends Controller
+ {
+ public function newAction(Request $request)
{
+ $securityContext = $this->container->get('security.context');
+ $form = $this->createForm(
+ new FriendMessageFormType($securityContext)
+ );
+
+ // ...
}
}
-Using the form
---------------
-
-Our form is now ready to use. We have two possible ways to use it inside of a
-controller. Either by creating it everytime and remembering to pass the security
-context, or by defining it as a service. This is the option we will show here.
+b) Defining the Form as a Service
+.................................
-To define your form as a service, you simply add the configuration to your
-configuration.
+To define your form as a service, just create a normal service and then tag
+it with :ref:`dic-tags-form-type`.
.. configuration-block::
.. code-block:: yaml
# app/config/config.yml
- acme.form.friend_message:
- class: Acme\DemoBundle\FormType\FriendMessageType
- arguments: [@security.context]
- tags:
- - { name: form.type, alias: acme_friend_message}
+ services:
+ acme.form.friend_message:
+ class: Acme\DemoBundle\Form\Type\FriendMessageFormType
+ arguments: [@security.context]
+ tags:
+ -
+ name: form.type
+ alias: acme_friend_message
.. code-block:: xml
<!-- app/config/config.xml -->
<services>
- <service id="acme.form.friend_message" class="Acme\DemoBundle\FormType\FriendMessageType">
+ <service id="acme.form.friend_message" class="Acme\DemoBundle\Form\Type\FriendMessageFormType">
<argument type="service" id="security.context" />
<tag name="form.type" alias="acme_friend_message" />
</service>
@@ -319,7 +367,7 @@ configuration.
.. code-block:: php
// app/config/config.php
- $definition = new Definition('Acme\DemoBundle\FormType\FriendMessageType');
+ $definition = new Definition('Acme\DemoBundle\Form\Type\FriendMessageFormType');
$definition->addTag('form.type', array('alias' => 'acme_friend_message'));
$container->setDefinition(
'acme.form.friend_message',
@@ -327,39 +375,44 @@ configuration.
array('security.context')
);
-By adding the form as a service, we make sure that this form can now be used
-simply from anywhere. If you need to add it to another form, you will just need
-to use::
-
- $builder->add('message', 'acme_friend_message');
-
If you wish to create it from within a controller or any other service that has
access to the form factory, you then use::
- // src/AcmeDemoBundle/Controller/FriendMessageController.php
- public function friendMessageAction()
+ class FriendMessageController extends Controller
{
- $form = $this->get('form.factory')->create('acme_friend_message');
- $form = $form->createView();
+ public function newAction(Request $request)
+ {
+ $form = $this->createForm('acme_friend_message');
+
+ // ...
+ }
+ }
+
+You can also easily embed the form type into another form::
- return compact('form');
+ // inside some other "form type" class
+ public function buildForm(FormBuilderInterface $builder, array $options)
+ {
+ $builder->add('message', 'acme_friend_message');
}
-Dynamic generation for submitted forms
-======================================
+.. _cookbook-form-events-submitted-data:
+
+Dynamic generation for submitted Forms
+--------------------------------------
-An other case that can appear is that you want to customize the form specific to
-the data that was submitted by the user. If we take as an example a registration
+Another case that can appear is that you want to customize the form specific to
+the data that was submitted by the user. For example, imagine you have a registration
form for sports gatherings. Some events will allow you to specify your preferred
-position on the field. This would be a choice field for example. However the
+position on the field. This would be a ``choice`` field for example. However the
possible choices will depend on each sport. Football will have attack, defense,
-goalkeeper etc... Baseball will have a pitcher but will not have goalkeeper. We
+goalkeeper etc... Baseball will have a pitcher but will not have goalkeeper. You
will need the correct options to be set in order for validation to pass.
The meetup is passed as an entity hidden field to the form. So we can access each
sport like this::
- // src/Acme/DemoBundle/FormType/SportMeetupType.php
+ // src/Acme/DemoBundle/Form/Type/SportMeetupType.php
class SportMeetupType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
@@ -374,7 +427,11 @@ sport like this::
FormEvents::PRE_SET_DATA,
function(FormEvent $event) use($user, $factory){
$form = $event->getForm();
- $event->getData()->getSport()->getAvailablePositions();
+
+ // this would be your entity, i.e. SportMeetup
+ $data = $event->getData();
+
+ $positions = $data->getSport()->getAvailablePositions();
// ... proceed with customizing the form based on available positions
}
@@ -382,35 +439,42 @@ sport like this::
}
}
+When you're building this form to display to the user for the first time,
+then this example works perfectly.
+
+However, things get more difficult when you handle the form submission. This
+is be cause the ``PRE_SET_DATA`` event tells us the data that you're starting
+with (e.g. an empty ``SportMeetup`` object), *not* the submitted data.
-While generating this kind of form to display it to the user for the first time,
-we can just as previously, use a simple listener and all goes fine.
+On a form, we can usually listen to the following events:
-When considering form submission, things are usually a bit different because
-subscribing to PRE_SET_DATA will only return us an empty ``SportMeetup`` object.
-That object will then be populated with the data sent by the user when there is a
-call to ``$form->bind($request)``.
+* ``PRE_SET_DATA``
+* ``POST_SET_DATA``
+* ``PRE_BIND``
+* ``BIND``
+* ``POST_BIND``
-On a form, we can usually listen to the following events::
+When listening to ``BIND`` and ``POST_BIND``, it's already "too late" to make
+changes to the form. Fortunately, ``PRE_BIND`` is perfect for this. There
+is, however, a big difference in what ``$event->getData()`` returns for each
+of these events. Specifically, in ``PRE_BIND``, ``$event->getData()`` returns
+the raw data submitted by the user.
- * ``PRE_SET_DATA``
- * ``POST_SET_DATA``
- * ``PRE_BIND``
- * ``BIND``
- * ``POST_BIND``
+This can be used to get the ``SportMeetup`` id and retrieve it from the database,
+given you have a reference to the object manager (if using doctrine). In
+the end, you have an event subscriber that listens to two different events,
+requires some external services and customizes the form. In such a situation,
+it's probably better to define this as a service rather than using an anonymouse
+function as the event listener callback.
-When listening to bind and post-bind, it's already "too late" to make changes to
-the form. But pre-bind is fine. There is however a big difference in what
-``$event->getData()`` will return for each of these events as pre-bind will return
-an array instead of an object. This is the raw data submitted by the user.
+The subscriber would now look like::
-This can be used to get the SportMeetup's id and retrieve it from the database,
-given we have a reference to our object manager (if using doctrine). So we have
-an event subscriber that listens to two different events, requires some
-external services and customizes our form. In such a situation, it seems cleaner
-to define this as a service rather than use closure like in the previous example.
+ // src/Acme/DemoBundle/Form/EventListener/RegistrationSportListener.php
+ namespace Acme\DemoBundle\Form\EventListener;
-Our subscriber would now look like::
+ use Symfony\Component\Form\FormFactoryInterface;
+ use Doctrine\ORM\EntityManager;
+ use Symfony\Component\Form\FormEvent;
class RegistrationSportListener implements EventSubscriberInterface
{
@@ -420,14 +484,14 @@ Our subscriber would now look like::
private $factory;
/**
- * @var DocumentManager
+ * @var EntityManager
*/
private $om;
/**
* @param factory FormFactoryInterface
*/
- public function __construct(FormFactoryInterface $factory, ObjectManager $om)
+ public function __construct(FormFactoryInterface $factory, EntityManager $om)
{
$this->factory = $factory;
$this->om = $om;
@@ -435,16 +499,16 @@ Our subscriber would now look like::
public static function getSubscribedEvents()
{
- return [
+ return array(
FormEvents::PRE_BIND => 'preBind',
FormEvents::PRE_SET_DATA => 'preSetData',
- ];
+ );
}
/**
- * @param event DataEvent
+ * @param event FormEvent
*/
- public function preSetData(DataEvent $event)
+ public function preSetData(FormEvent $event)
{
$meetup = $event->getData()->getMeetup();
@@ -454,19 +518,20 @@ Our subscriber would now look like::
}
$form = $event->getForm();
- $positions = $meetup->getSport()->getPostions();
+ $positions = $meetup->getSport()->getPositions();
$this->customizeForm($form, $positions);
}
- public function preBind(DataEvent $event)
+ public function preBind(FormEvent $event)
{
$data = $event->getData();
$id = $data['event'];
$meetup = $this->om
- ->getRepository('Acme\SportBundle\Document\Event')
- ->find($id);
- if($meetup === null){
+ ->getRepository('AcmeDemoBundle:SportMeetup')
+ ->find($id);
+
+ if ($meetup === null) {
$msg = 'The event %s could not be found for you registration';
throw new \Exception(sprintf($msg, $id));
}
@@ -482,12 +547,12 @@ Our subscriber would now look like::
}
}
-We can see that we need to listen on these two events and have different callbacks
-only because in two different scenarios, the data that we can use is given in a
+You can see that you need to listen on these two events and have different callbacks
+only because in two different scenarios, the data that you can use is given in a
different format. Other than that, this class always performs exactly the same
things on a given form.
-Now that we have this set up, we need to create our services:
+Now that you have that setup, register your form and the listener as services:
.. configuration-block::
@@ -495,23 +560,23 @@ Now that we have this set up, we need to create our services:
# app/config/config.yml
acme.form.sport_meetup:
- class: Acme\SportBundle\FormType\RegistrationType
+ class: Acme\SportBundle\Form\Type\SportMeetupType
arguments: [@acme.form.meetup_registration_listener]
tags:
- { name: form.type, alias: acme_meetup_registration }
acme.form.meetup_registration_listener
- class: Acme\SportBundle\Form\RegistrationSportListener
+ class: Acme\SportBundle\Form\EventListener\RegistrationSportListener
arguments: [@form.factory, @doctrine]
.. code-block:: xml
<!-- app/config/config.xml -->
<services>
- <service id="acme.form.sport_meetup" class="Acme\SportBundle\FormType\RegistrationType">
+ <service id="acme.form.sport_meetup" class="Acme\SportBundle\FormType\SportMeetupType">
<argument type="service" id="acme.form.meetup_registration_listener" />
<tag name="form.type" alias="acme_meetup_registration" />
</service>
- <service id="acme.form.meetup_registration_listener" class="Acme\SportBundle\Form\RegistrationSportListener">
+ <service id="acme.form.meetup_registration_listener" class="Acme\SportBundle\Form\EventListener\RegistrationSportListener">
<argument type="service" id="form.factory" />
<argument type="service" id="doctrine" />
</service>
@@ -520,24 +585,44 @@ Now that we have this set up, we need to create our services:
.. code-block:: php
// app/config/config.php
- $definition = new Definition('Acme\SportBundle\FormType\RegistrationType');
+ $definition = new Definition('Acme\SportBundle\Form\Type\SportMeetupType');
$definition->addTag('form.type', array('alias' => 'acme_meetup_registration'));
$container->setDefinition(
'acme.form.meetup_registration_listener',
$definition,
array('security.context')
);
- $definition = new Definition('Acme\SportBundle\Form\RegistrationSportListener');
+ $definition = new Definition('Acme\SportBundle\Form\EventListener\RegistrationSportListener');
$container->setDefinition(
'acme.form.meetup_registration_listener',
$definition,
array('form.factory', 'doctrine')
);
-And this should tie everything together. We can now retrieve our form from the
+In this setup, the ``RegistrationSportListener`` will be a constructor argument
+to ``SportMeetupType``. You can then register it as an event subscriber on
+your form::
+
+ private $registrationSportListener;
+
+ public function __construct(RegistrationSportListener $registrationSportListener)
+ {
+ $this->registrationSportListener = $registrationSportListener;
+ }
+
+ public function buildForm(FormBuilderInterface $builder, array $options)
+ {
+ // ...
+ $subscriber = new AddNameFieldSubscriber($builder->getFormFactory());
+ $builder->addEventSubscriber($this->registrationSportListener);
+ }
+
+And this should tie everything together. You can now retrieve your form from the
controller, display it to a user, and validate it with the right choice options
set for every possible kind of sport that our users are registering for.
-.. _`DataEvent`: https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/Event/DataEvent.php
-.. _`FormEvents class`: https://github.com/symfony/Form/blob/master/FormEvents.php
-.. _`Form class`: https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/Form.php
+One piece that may still be missing is the client-side updating of your form
+after the sport is selected. This should be handled by making an AJAX call
+back to your application. In that controller, you can bind your form, but
+instead of processing it, simply use the bound form to render the updated
+fields. The response from the AJAX call can then be used to update the view.
2  reference/dic_tags.rst
View
@@ -63,6 +63,8 @@ data_collector
For details on creating your own custom data collection, read the cookbook
article: :doc:`/cookbook/profiler/data_collector`.
+.. _dic-tags-form-type:
+
form.type
---------
Something went wrong with that request. Please try again.