Skip to content
This repository
Browse code

Merge pull request #2417 from symfony/dynamic-forms

Refactor: Dynamic forms cookbook entry
  • Loading branch information...
commit d9becd23c16ba4c61c3a2a7a7acc2264645bd6e0 2 parents 0d62bfc + cc1c01c
Ryan Weaver authored April 03, 2013
2  cookbook/form/create_custom_field_type.rst
Source Rendered
@@ -207,6 +207,8 @@ But this only works because the ``GenderType()`` is very simple. What if
207 207
 the gender codes were stored in configuration or in a database? The next
208 208
 section explains how more complex field types solve this problem.
209 209
 
  210
+.. _form-cookbook-form-field-service:
  211
+
210 212
 Creating your Field Type as a Service
211 213
 -------------------------------------
212 214
 
399  cookbook/form/dynamic_form_modification.rst
Source Rendered
@@ -2,7 +2,32 @@
2 2
    single: Form; Events
3 3
 
4 4
 How to Dynamically Modify Forms Using Form Events
5  
-===================================================
  5
+=================================================
  6
+
  7
+Often times, a form can't be created statically. In this entry, you'll learn
  8
+how to customize your form based on three common use-cases:
  9
+
  10
+1) :ref:`cookbook-form-events-underlying-data`
  11
+
  12
+Example: you have a "Product" form and need to modify/add/remove a field
  13
+based on the data on the underlying Product being edited.
  14
+
  15
+2) :ref:`cookbook-form-events-user-data`
  16
+
  17
+Example: you create a "Friend Message" form and need to build a drop-down
  18
+that contains only users that are friends with the *current* authenticated
  19
+user.
  20
+
  21
+3) :ref:`cookbook-form-events-submitted-data`
  22
+
  23
+Example: on a registration form, you have a "country" field and a "state"
  24
+field which should populate dynamically based on the value in the "country"
  25
+field.
  26
+
  27
+.. _cookbook-form-events-underlying-data:
  28
+
  29
+Customizing your Form based on the underlying Data
  30
+--------------------------------------------------
6 31
 
7 32
 Before jumping right into dynamic form generation, let's have a quick review
8 33
 of what a bare form class looks like::
@@ -12,6 +37,7 @@ of what a bare form class looks like::
12 37
 
13 38
     use Symfony\Component\Form\AbstractType;
14 39
     use Symfony\Component\Form\FormBuilderInterface;
  40
+    use Symfony\Component\OptionsResolver\OptionsResolverInterface;
15 41
 
16 42
     class ProductType extends AbstractType
17 43
     {
@@ -21,6 +47,13 @@ of what a bare form class looks like::
21 47
             $builder->add('price');
22 48
         }
23 49
 
  50
+        public function setDefaultOptions(OptionsResolverInterface $resolver)
  51
+        {
  52
+            $resolver->setDefaults(array(
  53
+                'data_class' => 'Acme\DemoBundle\Entity\Product'
  54
+            ));
  55
+        }
  56
+
24 57
         public function getName()
25 58
         {
26 59
             return 'product';
@@ -33,9 +66,9 @@ of what a bare form class looks like::
33 66
     probably need to take a step back and first review the :doc:`Forms chapter </book/forms>`
34 67
     before proceeding.
35 68
 
36  
-Let's assume for a moment that this form utilizes an imaginary "Product" class
37  
-that has only two relevant properties ("name" and "price"). The form generated
38  
-from this class will look the exact same regardless if a new Product is being created
  69
+Assume for a moment that this form utilizes an imaginary "Product" class
  70
+that has only two properties ("name" and "price"). The form generated from
  71
+this class will look the exact same regardless if a new Product is being created
39 72
 or if an existing product is being edited (e.g. a product fetched from the database).
40 73
 
41 74
 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.
48 81
 .. _`cookbook-forms-event-subscriber`:
49 82
 
50 83
 Adding An Event Subscriber To A Form Class
51  
-------------------------------------------
  84
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
52 85
 
53 86
 So, instead of directly adding that "name" widget via your ProductType form
54 87
 class, let's delegate the responsibility of creating that particular field
@@ -57,8 +90,7 @@ to an Event Subscriber::
57 90
     // src/Acme/DemoBundle/Form/Type/ProductType.php
58 91
     namespace Acme\DemoBundle\Form\Type;
59 92
 
60  
-    use Symfony\Component\Form\AbstractType;
61  
-    use Symfony\Component\Form\FormBuilderInterface;
  93
+    // ...
62 94
     use Acme\DemoBundle\Form\EventListener\AddNameFieldSubscriber;
63 95
 
64 96
     class ProductType extends AbstractType
@@ -70,10 +102,7 @@ to an Event Subscriber::
70 102
             $builder->add('price');
71 103
         }
72 104
 
73  
-        public function getName()
74  
-        {
75  
-            return 'product';
76  
-        }
  105
+        // ...
77 106
     }
78 107
 
79 108
 The event subscriber is passed the FormFactory object in its constructor so
@@ -83,7 +112,7 @@ notified of the dispatched event during form creation.
83 112
 .. _`cookbook-forms-inside-subscriber-class`:
84 113
 
85 114
 Inside the Event Subscriber Class
86  
----------------------------------
  115
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
87 116
 
88 117
 The goal is to create a "name" field *only* if the underlying Product object
89 118
 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::
118 147
             $data = $event->getData();
119 148
             $form = $event->getForm();
120 149
 
121  
-            // During form creation setData() is called with null as an argument
122  
-            // by the FormBuilder constructor. You're only concerned with when
123  
-            // setData is called with an actual Entity object in it (whether new
124  
-            // or fetched with Doctrine). This if statement lets you skip right
125  
-            // over the null condition.
126  
-            if (null === $data) {
127  
-                return;
128  
-            }
129  
-
130 150
             // check if the product object is "new"
131  
-            if (!$data->getId()) {
  151
+            // If you didn't pass any data to the form, the data is "null".
  152
+            // This should be considered a new "Product"
  153
+            if (!$data || !$data->getId()) {
132 154
                 $form->add($this->factory->createNamed('name', 'text'));
133 155
             }
134 156
         }
135 157
     }
136 158
 
137  
-.. caution::
138  
-
139  
-    It is easy to misunderstand the purpose of the ``if (null === $data)`` segment
140  
-    of this event subscriber. To fully understand its role, you might consider
141  
-    also taking a look at the `Form class`_ and paying special attention to
142  
-    where setData() is called at the end of the constructor, as well as the
143  
-    setData() method itself.
144  
-
145  
-The ``FormEvents::PRE_SET_DATA`` line actually resolves to the string ``form.pre_set_data``.
146  
-The `FormEvents class`_ serves an organizational purpose. It is a centralized location
147  
-in which you can find all of the various form events available.
  159
+.. tip::
148 160
 
149  
-While this example could have used the ``form.post_set_data``
150  
-event just as effectively, by using ``form.pre_set_data`` you guarantee that
151  
-the data being retrieved from the ``Event`` object has in no way been modified
152  
-by any other subscribers or listeners because ``form.pre_set_data`` is the
153  
-first form event dispatched.
  161
+    The ``FormEvents::PRE_SET_DATA`` line actually resolves to the string
  162
+    ``form.pre_set_data``. :class:`Symfony\\Component\\Form\\FormEvents` serves
  163
+    an organizational purpose. It is a centralized location in which you can
  164
+    find all of the various form events available.
154 165
 
155 166
 .. note::
156 167
 
157  
-    You may view the full list of form events via the `FormEvents class`_,
158  
-    found in the form bundle.
  168
+    You can view the full list of form events via the :class:`Symfony\\Component\\Form\\FormEvents`
  169
+    class.
159 170
 
160  
-How to Dynamically Generate Forms based on user data
161  
-====================================================
  171
+.. _cookbook-form-events-user-data:
  172
+
  173
+How to Dynamically Generate Forms based on user Data
  174
+----------------------------------------------------
162 175
 
163 176
 Sometimes you want a form to be generated dynamically based not only on data
164  
-from this form but also on something else. For example depending on the user
165  
-currently using the application. If you have a social website where a user can
166  
-only message people who are his friends on the website, then the current user
167  
-doesn't need to be included as a field of your form, but a "choice list" of
168  
-whom to message should only contain users that are the current user's friends.
  177
+from the form but also on something else - like some data from the current user.
  178
+Suppose you have a social website where a user can only message people who
  179
+are his friends on the website. In this case, a "choice list" of whom to message
  180
+should only contain users that are the current user's friends.
169 181
 
170  
-Creating the form type
171  
-----------------------
  182
+Creating the Form Type
  183
+~~~~~~~~~~~~~~~~~~~~~~
172 184
 
173  
-Using an event listener, our form could be built like this::
  185
+Using an event listener, your form might look like this::
174 186
 
175  
-    // src/Acme/DemoBundle/FormType/FriendMessageFormType.php
176  
-    namespace Acme\DemoBundle\FormType;
  187
+    // src/Acme/DemoBundle/Form/Type/FriendMessageFormType.php
  188
+    namespace Acme\DemoBundle\Form\Type;
177 189
 
178 190
     use Symfony\Component\Form\AbstractType;
179 191
     use Symfony\Component\Form\FormBuilderInterface;
@@ -181,7 +193,6 @@ Using an event listener, our form could be built like this::
181 193
     use Symfony\Component\Form\FormEvent;
182 194
     use Symfony\Component\Security\Core\SecurityContext;
183 195
     use Symfony\Component\OptionsResolver\OptionsResolverInterface;
184  
-    use Acme\DemoBundle\FormSubscriber\UserListener;
185 196
 
186 197
     class FriendMessageFormType extends AbstractType
187 198
     {
@@ -206,13 +217,11 @@ Using an event listener, our form could be built like this::
206 217
         }
207 218
     }
208 219
 
209  
-The problem is now to get the current application user and create a choice field
210  
-that would contain only this user's friends.
  220
+The problem is now to get the current user and create a choice field that
  221
+contains only this user's friends.
211 222
 
212 223
 Luckily it is pretty easy to inject a service inside of the form. This can be
213  
-done in the constructor.
214  
-
215  
-.. code-block:: php
  224
+done in the constructor::
216 225
 
217 226
     private $securityContext;
218 227
 
@@ -223,19 +232,26 @@ done in the constructor.
223 232
 
224 233
 .. note::
225 234
 
226  
-    You might wonder, now that we have access to the User (through) the security
227  
-    context, why don't we just use that inside of the buildForm function and
228  
-    still use a listener?
229  
-    This is because doing so in the buildForm method would result in the whole
230  
-    form type being modified and not only one form instance.
  235
+    You might wonder, now that you have access to the User (through the security
  236
+    context), why not just use it directly in ``buildForm`` and omit the
  237
+    event listener? This is because doing so in the ``buildForm`` method
  238
+    would result in the whole form type being modified and not just this
  239
+    one form instance. This may not usually be a problem, but technically
  240
+    a single form type could be used on a single request to create many forms
  241
+    or fields.
231 242
 
232  
-Customizing the form type
233  
--------------------------
  243
+Customizing the Form Type
  244
+~~~~~~~~~~~~~~~~~~~~~~~~~
234 245
 
235  
-Now that we have all the basics in place, we can put everything in place and add
236  
-our listener::
  246
+Now that you have all the basics in place you an take advantage of the ``securityContext``
  247
+and fill in the listener logic::
237 248
 
238 249
     // src/Acme/DemoBundle/FormType/FriendMessageFormType.php
  250
+
  251
+    use Symfony\Component\Security\Core\SecurityContext;
  252
+    use Doctrine\ORM\EntityRepository;
  253
+    // ...
  254
+
239 255
     class FriendMessageFormType extends AbstractType
240 256
     {
241 257
         private $securityContext;
@@ -251,66 +267,98 @@ our listener::
251 267
                 ->add('subject', 'text')
252 268
                 ->add('body', 'textarea')
253 269
             ;
  270
+
  271
+            // grab the user, do a quick sanity check that one exists
254 272
             $user = $this->securityContext->getToken()->getUser();
  273
+            if (!$user) {
  274
+                throw new \LogicException(
  275
+                    'The FriendMessageFormType cannot be used without an authenticated user!'
  276
+                );
  277
+            }
  278
+
255 279
             $factory = $builder->getFormFactory();
256 280
 
257 281
             $builder->addEventListener(
258 282
                 FormEvents::PRE_SET_DATA,
259 283
                 function(FormEvent $event) use($user, $factory){
260 284
                     $form = $event->getForm();
261  
-                    $userId = $user->getId();
262 285
 
263 286
                     $formOptions = array(
264  
-                        'class' => 'Acme\DemoBundle\Document\User',
  287
+                        'class' => 'Acme\DemoBundle\Entity\User',
265 288
                         'multiple' => false,
266 289
                         'expanded' => false,
267 290
                         'property' => 'fullName',
268  
-                        'query_builder' => function(DocumentRepository $dr) use ($userId) {
269  
-                            return $dr->createQueryBuilder()->field('friends.$id')->equals(new \MongoId($userId));
  291
+                        'query_builder' => function(EntityRepository $er) use ($user) {
  292
+                            // build a custom query, or call a method on your repository (even better!)
270 293
                         },
271 294
                     );
272 295
 
273  
-                    $form->add($factory->createNamed('friend', 'document', null, $formOptions));
  296
+                    // create the field, this is similar the $builder->add()
  297
+                    // field name, field type, data, options
  298
+                    $form->add($factory->createNamed('friend', 'entity', null, $formOptions));
274 299
                 }
275 300
             );
276 301
         }
277 302
 
278  
-        public function getName()
279  
-        {
280  
-            return 'acme_friend_message';
281  
-        }
  303
+        // ...
  304
+    }
282 305
 
283  
-        public function setDefaultOptions(OptionsResolverInterface $resolver)
  306
+Using the Form
  307
+~~~~~~~~~~~~~~
  308
+
  309
+Our form is now ready to use and there are two possible ways to use it inside
  310
+of a controller:
  311
+
  312
+a) create it manually and remember to pass the security context to it;
  313
+
  314
+or
  315
+
  316
+b) define it as a service.
  317
+
  318
+a) Creating the Form manually
  319
+.............................
  320
+
  321
+This is very simple, and is probably the better approach unless you're using
  322
+your new form type in many places or embedding it into other forms::
  323
+
  324
+    class FriendMessageController extends Controller
  325
+    {
  326
+        public function newAction(Request $request)
284 327
         {
  328
+            $securityContext = $this->container->get('security.context');
  329
+            $form = $this->createForm(
  330
+                new FriendMessageFormType($securityContext)
  331
+            );
  332
+
  333
+            // ...
285 334
         }
286 335
     }
287 336
 
288  
-Using the form
289  
---------------
290  
-
291  
-Our form is now ready to use. We have two possible ways to use it inside of a
292  
-controller. Either by creating it everytime and remembering to pass the security
293  
-context, or by defining it as a service. This is the option we will show here.
  337
+b) Defining the Form as a Service
  338
+.................................
294 339
 
295  
-To define your form as a service, you simply add the configuration to your
296  
-configuration.
  340
+To define your form as a service, just create a normal service and then tag
  341
+it with :ref:`dic-tags-form-type`.
297 342
 
298 343
 .. configuration-block::
299 344
 
300 345
     .. code-block:: yaml
301 346
 
302 347
         # app/config/config.yml
303  
-        acme.form.friend_message:
304  
-            class: Acme\DemoBundle\FormType\FriendMessageType
305  
-            arguments: [@security.context]
306  
-            tags:
307  
-                - { name: form.type, alias: acme_friend_message}
  348
+        services:
  349
+            acme.form.friend_message:
  350
+                class: Acme\DemoBundle\Form\Type\FriendMessageFormType
  351
+                arguments: [@security.context]
  352
+                tags:
  353
+                    -
  354
+                        name: form.type
  355
+                        alias: acme_friend_message
308 356
 
309 357
     .. code-block:: xml
310 358
 
311 359
         <!-- app/config/config.xml -->
312 360
         <services>
313  
-            <service id="acme.form.friend_message" class="Acme\DemoBundle\FormType\FriendMessageType">
  361
+            <service id="acme.form.friend_message" class="Acme\DemoBundle\Form\Type\FriendMessageFormType">
314 362
                 <argument type="service" id="security.context" />
315 363
                 <tag name="form.type" alias="acme_friend_message" />
316 364
             </service>
@@ -319,7 +367,7 @@ configuration.
319 367
     .. code-block:: php
320 368
 
321 369
         // app/config/config.php
322  
-        $definition = new Definition('Acme\DemoBundle\FormType\FriendMessageType');
  370
+        $definition = new Definition('Acme\DemoBundle\Form\Type\FriendMessageFormType');
323 371
         $definition->addTag('form.type', array('alias' => 'acme_friend_message'));
324 372
         $container->setDefinition(
325 373
             'acme.form.friend_message',
@@ -327,39 +375,44 @@ configuration.
327 375
             array('security.context')
328 376
         );
329 377
 
330  
-By adding the form as a service, we make sure that this form can now be used
331  
-simply from anywhere. If you need to add it to another form, you will just need
332  
-to use::
333  
-
334  
-    $builder->add('message', 'acme_friend_message');
335  
-
336 378
 If you wish to create it from within a controller or any other service that has
337 379
 access to the form factory, you then use::
338 380
 
339  
-    // src/AcmeDemoBundle/Controller/FriendMessageController.php
340  
-    public function friendMessageAction()
  381
+    class FriendMessageController extends Controller
341 382
     {
342  
-        $form = $this->get('form.factory')->create('acme_friend_message');
343  
-        $form = $form->createView();
  383
+        public function newAction(Request $request)
  384
+        {
  385
+            $form = $this->createForm('acme_friend_message');
  386
+
  387
+            // ...
  388
+        }
  389
+    }
  390
+
  391
+You can also easily embed the form type into another form::
344 392
 
345  
-        return compact('form');
  393
+    // inside some other "form type" class
  394
+    public function buildForm(FormBuilderInterface $builder, array $options)
  395
+    {
  396
+        $builder->add('message', 'acme_friend_message');
346 397
     }
347 398
 
348  
-Dynamic generation for submitted forms
349  
-======================================
  399
+.. _cookbook-form-events-submitted-data:
  400
+
  401
+Dynamic generation for submitted Forms
  402
+--------------------------------------
350 403
 
351  
-An other case that can appear is that you want to customize the form specific to
352  
-the data that was submitted by the user. If we take as an example a registration
  404
+Another case that can appear is that you want to customize the form specific to
  405
+the data that was submitted by the user. For example, imagine you have a registration
353 406
 form for sports gatherings. Some events will allow you to specify your preferred
354  
-position on the field. This would be a choice field for example. However the
  407
+position on the field. This would be a ``choice`` field for example. However the
355 408
 possible choices will depend on each sport. Football will have attack, defense,
356  
-goalkeeper etc... Baseball will have a pitcher but will not have goalkeeper. We
  409
+goalkeeper etc... Baseball will have a pitcher but will not have goalkeeper. You
357 410
 will need the correct options to be set in order for validation to pass.
358 411
 
359 412
 The meetup is passed as an entity hidden field to the form. So we can access each
360 413
 sport like this::
361 414
 
362  
-    // src/Acme/DemoBundle/FormType/SportMeetupType.php
  415
+    // src/Acme/DemoBundle/Form/Type/SportMeetupType.php
363 416
     class SportMeetupType extends AbstractType
364 417
     {
365 418
         public function buildForm(FormBuilderInterface $builder, array $options)
@@ -374,7 +427,11 @@ sport like this::
374 427
                 FormEvents::PRE_SET_DATA,
375 428
                 function(FormEvent $event) use($user, $factory){
376 429
                     $form = $event->getForm();
377  
-                    $event->getData()->getSport()->getAvailablePositions();
  430
+
  431
+                    // this would be your entity, i.e. SportMeetup
  432
+                    $data = $event->getData();
  433
+
  434
+                    $positions = $data->getSport()->getAvailablePositions();
378 435
 
379 436
                     // ... proceed with customizing the form based on available positions
380 437
                 }
@@ -382,35 +439,42 @@ sport like this::
382 439
         }
383 440
     }
384 441
 
  442
+When you're building this form to display to the user for the first time,
  443
+then this example works perfectly.
  444
+
  445
+However, things get more difficult when you handle the form submission. This
  446
+is be cause the ``PRE_SET_DATA`` event tells us the data that you're starting
  447
+with (e.g. an empty ``SportMeetup`` object), *not* the submitted data.
385 448
 
386  
-While generating this kind of form to display it to the user for the first time,
387  
-we can just as previously, use a simple listener and all goes fine.
  449
+On a form, we can usually listen to the following events:
388 450
 
389  
-When considering form submission, things are usually a bit different because
390  
-subscribing to PRE_SET_DATA will only return us an empty ``SportMeetup`` object.
391  
-That object will then be populated with the data sent by the user when there is a
392  
-call to ``$form->bind($request)``.
  451
+* ``PRE_SET_DATA``
  452
+* ``POST_SET_DATA``
  453
+* ``PRE_BIND``
  454
+* ``BIND``
  455
+* ``POST_BIND``
393 456
 
394  
-On a form, we can usually listen to the following events::
  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.
395 462
 
396  
- * ``PRE_SET_DATA``
397  
- * ``POST_SET_DATA``
398  
- * ``PRE_BIND``
399  
- * ``BIND``
400  
- * ``POST_BIND``
  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 anonymouse
  468
+function as the event listener callback.
401 469
 
402  
-When listening to bind and post-bind, it's already "too late" to make changes to
403  
-the form. But pre-bind is fine. There is however a big difference in what
404  
-``$event->getData()`` will return for each of these events as pre-bind will return
405  
-an array instead of an object. This is the raw data submitted by the user.
  470
+The subscriber would now look like::
406 471
 
407  
-This can be used to get the SportMeetup's id and retrieve it from the database,
408  
-given we have a reference to our object manager (if using doctrine). So we have
409  
-an event subscriber that listens to two different events, requires some
410  
-external services and customizes our form. In such a situation, it seems cleaner
411  
-to define this as a service rather than use closure like in the previous example.
  472
+    // src/Acme/DemoBundle/Form/EventListener/RegistrationSportListener.php
  473
+    namespace Acme\DemoBundle\Form\EventListener;
412 474
 
413  
-Our subscriber would now look like::
  475
+    use Symfony\Component\Form\FormFactoryInterface;
  476
+    use Doctrine\ORM\EntityManager;
  477
+    use Symfony\Component\Form\FormEvent;
414 478
 
415 479
     class RegistrationSportListener implements EventSubscriberInterface
416 480
     {
@@ -420,14 +484,14 @@ Our subscriber would now look like::
420 484
         private $factory;
421 485
 
422 486
         /**
423  
-         * @var DocumentManager
  487
+         * @var EntityManager
424 488
          */
425 489
         private $om;
426 490
 
427 491
         /**
428 492
          * @param factory FormFactoryInterface
429 493
          */
430  
-        public function __construct(FormFactoryInterface $factory, ObjectManager $om)
  494
+        public function __construct(FormFactoryInterface $factory, EntityManager $om)
431 495
         {
432 496
             $this->factory = $factory;
433 497
             $this->om = $om;
@@ -435,16 +499,16 @@ Our subscriber would now look like::
435 499
 
436 500
         public static function getSubscribedEvents()
437 501
         {
438  
-            return [
  502
+            return array(
439 503
                 FormEvents::PRE_BIND => 'preBind',
440 504
                 FormEvents::PRE_SET_DATA => 'preSetData',
441  
-            ];
  505
+            );
442 506
         }
443 507
 
444 508
         /**
445  
-         * @param event DataEvent
  509
+         * @param event FormEvent
446 510
          */
447  
-        public function preSetData(DataEvent $event)
  511
+        public function preSetData(FormEvent $event)
448 512
         {
449 513
             $meetup = $event->getData()->getMeetup();
450 514
 
@@ -454,19 +518,20 @@ Our subscriber would now look like::
454 518
             }
455 519
 
456 520
             $form = $event->getForm();
457  
-            $positions = $meetup->getSport()->getPostions();
  521
+            $positions = $meetup->getSport()->getPositions();
458 522
 
459 523
             $this->customizeForm($form, $positions);
460 524
         }
461 525
 
462  
-        public function preBind(DataEvent $event)
  526
+        public function preBind(FormEvent $event)
463 527
         {
464 528
             $data = $event->getData();
465 529
             $id = $data['event'];
466 530
             $meetup = $this->om
467  
-                        ->getRepository('Acme\SportBundle\Document\Event')
468  
-                        ->find($id);
469  
-            if($meetup === null){
  531
+                ->getRepository('AcmeDemoBundle:SportMeetup')
  532
+                ->find($id);
  533
+
  534
+            if ($meetup === null) {
470 535
                 $msg = 'The event %s could not be found for you registration';
471 536
                 throw new \Exception(sprintf($msg, $id));
472 537
             }
@@ -482,12 +547,12 @@ Our subscriber would now look like::
482 547
         }
483 548
     }
484 549
 
485  
-We can see that we need to listen on these two events and have different callbacks
486  
-only because in two different scenarios, the data that we can use is given in a
  550
+You can see that you need to listen on these two events and have different callbacks
  551
+only because in two different scenarios, the data that you can use is given in a
487 552
 different format. Other than that, this class always performs exactly the same
488 553
 things on a given form.
489 554
 
490  
-Now that we have this set up, we need to create our services:
  555
+Now that you have that setup, register your form and the listener as services:
491 556
 
492 557
 .. configuration-block::
493 558
 
@@ -495,23 +560,23 @@ Now that we have this set up, we need to create our services:
495 560
 
496 561
         # app/config/config.yml
497 562
         acme.form.sport_meetup:
498  
-            class: Acme\SportBundle\FormType\RegistrationType
  563
+            class: Acme\SportBundle\Form\Type\SportMeetupType
499 564
             arguments: [@acme.form.meetup_registration_listener]
500 565
             tags:
501 566
                 - { name: form.type, alias: acme_meetup_registration }
502 567
         acme.form.meetup_registration_listener
503  
-            class: Acme\SportBundle\Form\RegistrationSportListener
  568
+            class: Acme\SportBundle\Form\EventListener\RegistrationSportListener
504 569
             arguments: [@form.factory, @doctrine]
505 570
 
506 571
     .. code-block:: xml
507 572
 
508 573
         <!-- app/config/config.xml -->
509 574
         <services>
510  
-            <service id="acme.form.sport_meetup" class="Acme\SportBundle\FormType\RegistrationType">
  575
+            <service id="acme.form.sport_meetup" class="Acme\SportBundle\FormType\SportMeetupType">
511 576
                 <argument type="service" id="acme.form.meetup_registration_listener" />
512 577
                 <tag name="form.type" alias="acme_meetup_registration" />
513 578
             </service>
514  
-            <service id="acme.form.meetup_registration_listener" class="Acme\SportBundle\Form\RegistrationSportListener">
  579
+            <service id="acme.form.meetup_registration_listener" class="Acme\SportBundle\Form\EventListener\RegistrationSportListener">
515 580
                 <argument type="service" id="form.factory" />
516 581
                 <argument type="service" id="doctrine" />
517 582
             </service>
@@ -520,24 +585,44 @@ Now that we have this set up, we need to create our services:
520 585
     .. code-block:: php
521 586
 
522 587
         // app/config/config.php
523  
-        $definition = new Definition('Acme\SportBundle\FormType\RegistrationType');
  588
+        $definition = new Definition('Acme\SportBundle\Form\Type\SportMeetupType');
524 589
         $definition->addTag('form.type', array('alias' => 'acme_meetup_registration'));
525 590
         $container->setDefinition(
526 591
             'acme.form.meetup_registration_listener',
527 592
             $definition,
528 593
             array('security.context')
529 594
         );
530  
-        $definition = new Definition('Acme\SportBundle\Form\RegistrationSportListener');
  595
+        $definition = new Definition('Acme\SportBundle\Form\EventListener\RegistrationSportListener');
531 596
         $container->setDefinition(
532 597
             'acme.form.meetup_registration_listener',
533 598
             $definition,
534 599
             array('form.factory', 'doctrine')
535 600
         );
536 601
 
537  
-And this should tie everything together. We can now retrieve our form from the
  602
+In this setup, the ``RegistrationSportListener`` will be a constructor argument
  603
+to ``SportMeetupType``. You can then register it as an event subscriber on
  604
+your form::
  605
+
  606
+    private $registrationSportListener;
  607
+
  608
+    public function __construct(RegistrationSportListener $registrationSportListener)
  609
+    {
  610
+        $this->registrationSportListener = $registrationSportListener;
  611
+    }
  612
+
  613
+    public function buildForm(FormBuilderInterface $builder, array $options)
  614
+    {
  615
+        // ...
  616
+        $subscriber = new AddNameFieldSubscriber($builder->getFormFactory());
  617
+        $builder->addEventSubscriber($this->registrationSportListener);
  618
+    }
  619
+
  620
+And this should tie everything together. You can now retrieve your form from the
538 621
 controller, display it to a user, and validate it with the right choice options
539 622
 set for every possible kind of sport that our users are registering for.
540 623
 
541  
-.. _`DataEvent`: https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/Event/DataEvent.php
542  
-.. _`FormEvents class`: https://github.com/symfony/Form/blob/master/FormEvents.php
543  
-.. _`Form class`: https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/Form.php
  624
+One piece that may still be missing is the client-side updating of your form
  625
+after the sport is selected. This should be handled by making an AJAX call
  626
+back to your application. In that controller, you can bind your form, but
  627
+instead of processing it, simply use the bound form to render the updated
  628
+fields. The response from the AJAX call can then be used to update the view.
2  reference/dic_tags.rst
Source Rendered
@@ -63,6 +63,8 @@ data_collector
63 63
 For details on creating your own custom data collection, read the cookbook
64 64
 article: :doc:`/cookbook/profiler/data_collector`.
65 65
 
  66
+.. _dic-tags-form-type:
  67
+
66 68
 form.type
67 69
 ---------
68 70
 

0 notes on commit d9becd2

Please sign in to comment.
Something went wrong with that request. Please try again.