Skip to content
This repository
Browse code

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

…e is still quite a bit left to do
  • Loading branch information...
commit 0826e3389949dd97c7cd813725a94819f572d5d7 1 parent 0d62bfc
Ryan Weaver authored March 30, 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
 
398  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,44 @@ 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 an organizational purpose. It is a centralized location
  163
+    in which you can find all of the various form events available.
154 164
 
155 165
 .. note::
156 166
 
157  
-    You may view the full list of form events via the `FormEvents class`_,
158  
-    found in the form bundle.
  167
+    You can view the full list of form events via the :class:`Symfony\\Component\\Form\\FormEvents`
  168
+    class.
159 169
 
160  
-How to Dynamically Generate Forms based on user data
161  
-====================================================
  170
+.. _cookbook-form-events-user-data:
  171
+
  172
+How to Dynamically Generate Forms based on user Data
  173
+----------------------------------------------------
162 174
 
163 175
 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.
  176
+from the form but also on something else - like some data from the current user.
  177
+Suppose you have a social website where a user can only message people who
  178
+are his friends on the website. In this case, a "choice list" of whom to message
  179
+should only contain users that are the current user's friends.
169 180
 
170  
-Creating the form type
171  
-----------------------
  181
+Creating the Form Type
  182
+~~~~~~~~~~~~~~~~~~~~~~
172 183
 
173  
-Using an event listener, our form could be built like this::
  184
+Using an event listener, your form might look like this::
174 185
 
175  
-    // src/Acme/DemoBundle/FormType/FriendMessageFormType.php
176  
-    namespace Acme\DemoBundle\FormType;
  186
+    // src/Acme/DemoBundle/Form/Type/FriendMessageFormType.php
  187
+    namespace Acme\DemoBundle\Form\Type;
177 188
 
178 189
     use Symfony\Component\Form\AbstractType;
179 190
     use Symfony\Component\Form\FormBuilderInterface;
@@ -181,7 +192,6 @@ Using an event listener, our form could be built like this::
181 192
     use Symfony\Component\Form\FormEvent;
182 193
     use Symfony\Component\Security\Core\SecurityContext;
183 194
     use Symfony\Component\OptionsResolver\OptionsResolverInterface;
184  
-    use Acme\DemoBundle\FormSubscriber\UserListener;
185 195
 
186 196
     class FriendMessageFormType extends AbstractType
187 197
     {
@@ -206,13 +216,11 @@ Using an event listener, our form could be built like this::
206 216
         }
207 217
     }
208 218
 
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.
  219
+The problem is now to get the current user and create a choice field that
  220
+contains only this user's friends.
211 221
 
212 222
 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
  223
+done in the constructor::
216 224
 
217 225
     private $securityContext;
218 226
 
@@ -223,19 +231,26 @@ done in the constructor.
223 231
 
224 232
 .. note::
225 233
 
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.
  234
+    You might wonder, now that you have access to the User (through the security
  235
+    context), why not just use it directly in ``buildForm`` and omit the
  236
+    event listener? This is because doing so in the ``buildForm`` method
  237
+    would result in the whole form type being modified and not just this
  238
+    one form instance. This may not usually be a problem, but technically
  239
+    a single form type could be used on a single request to create many forms
  240
+    or fields.
231 241
 
232  
-Customizing the form type
233  
--------------------------
  242
+Customizing the Form Type
  243
+~~~~~~~~~~~~~~~~~~~~~~~~~
234 244
 
235  
-Now that we have all the basics in place, we can put everything in place and add
236  
-our listener::
  245
+Now that you have all the basics in place you an take advantage of the ``securityContext``
  246
+and fill in the listener logic::
237 247
 
238 248
     // src/Acme/DemoBundle/FormType/FriendMessageFormType.php
  249
+
  250
+    use Symfony\Component\Security\Core\SecurityContext;
  251
+    use Doctrine\ORM\EntityRepository;
  252
+    // ...
  253
+
239 254
     class FriendMessageFormType extends AbstractType
240 255
     {
241 256
         private $securityContext;
@@ -251,66 +266,98 @@ our listener::
251 266
                 ->add('subject', 'text')
252 267
                 ->add('body', 'textarea')
253 268
             ;
  269
+
  270
+            // grab the user, do a quick sanity check that one exists
254 271
             $user = $this->securityContext->getToken()->getUser();
  272
+            if (!$user) {
  273
+                throw new \LogicException(
  274
+                    'The FriendMessageFormType cannot be used without an authenticated user!'
  275
+                );
  276
+            }
  277
+
255 278
             $factory = $builder->getFormFactory();
256 279
 
257 280
             $builder->addEventListener(
258 281
                 FormEvents::PRE_SET_DATA,
259 282
                 function(FormEvent $event) use($user, $factory){
260 283
                     $form = $event->getForm();
261  
-                    $userId = $user->getId();
262 284
 
263 285
                     $formOptions = array(
264  
-                        'class' => 'Acme\DemoBundle\Document\User',
  286
+                        'class' => 'Acme\DemoBundle\Entity\User',
265 287
                         'multiple' => false,
266 288
                         'expanded' => false,
267 289
                         'property' => 'fullName',
268  
-                        'query_builder' => function(DocumentRepository $dr) use ($userId) {
269  
-                            return $dr->createQueryBuilder()->field('friends.$id')->equals(new \MongoId($userId));
  290
+                        'query_builder' => function(EntityRepository $er) use ($user) {
  291
+                            // build a custom query, or call a method on your repository (even better!)
270 292
                         },
271 293
                     );
272 294
 
273  
-                    $form->add($factory->createNamed('friend', 'document', null, $formOptions));
  295
+                    // create the field, this is similar the $builder->add()
  296
+                    // field name, field type, data, options
  297
+                    $form->add($factory->createNamed('friend', 'entity', null, $formOptions));
274 298
                 }
275 299
             );
276 300
         }
277 301
 
278  
-        public function getName()
279  
-        {
280  
-            return 'acme_friend_message';
281  
-        }
  302
+        // ...
  303
+    }
282 304
 
283  
-        public function setDefaultOptions(OptionsResolverInterface $resolver)
  305
+Using the Form
  306
+~~~~~~~~~~~~~~
  307
+
  308
+Our form is now ready to use and there are two possible ways to use it inside
  309
+of a controller:
  310
+
  311
+a) create it manually and remember to pass the security context to it;
  312
+
  313
+or
  314
+
  315
+b) define it as a service.
  316
+
  317
+a) Creating the Form manually
  318
+.............................
  319
+
  320
+This is very simple, and is probably the better approach unless you're using
  321
+your new form type in many places or embedding it into other forms::
  322
+
  323
+    class FriendMessageController extends Controller
  324
+    {
  325
+        public function newAction(Request $request)
284 326
         {
  327
+            $securityContext = $this->container->get('security.context');
  328
+            $form = $this->createForm(
  329
+                new FriendMessageFormType($securityContext)
  330
+            );
  331
+
  332
+            // ...
285 333
         }
286 334
     }
287 335
 
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.
  336
+b) Defining the Form as a Service
  337
+.................................
294 338
 
295  
-To define your form as a service, you simply add the configuration to your
296  
-configuration.
  339
+To define your form as a service, just create a normal service and then tag
  340
+it with :ref:`dic-tags-form-type`.
297 341
 
298 342
 .. configuration-block::
299 343
 
300 344
     .. code-block:: yaml
301 345
 
302 346
         # 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}
  347
+        services:
  348
+            acme.form.friend_message:
  349
+                class: Acme\DemoBundle\Form\Type\FriendMessageFormType
  350
+                arguments: [@security.context]
  351
+                tags:
  352
+                    -
  353
+                        name: form.type
  354
+                        alias: acme_friend_message
308 355
 
309 356
     .. code-block:: xml
310 357
 
311 358
         <!-- app/config/config.xml -->
312 359
         <services>
313  
-            <service id="acme.form.friend_message" class="Acme\DemoBundle\FormType\FriendMessageType">
  360
+            <service id="acme.form.friend_message" class="Acme\DemoBundle\Form\Type\FriendMessageFormType">
314 361
                 <argument type="service" id="security.context" />
315 362
                 <tag name="form.type" alias="acme_friend_message" />
316 363
             </service>
@@ -319,7 +366,7 @@ configuration.
319 366
     .. code-block:: php
320 367
 
321 368
         // app/config/config.php
322  
-        $definition = new Definition('Acme\DemoBundle\FormType\FriendMessageType');
  369
+        $definition = new Definition('Acme\DemoBundle\Form\Type\FriendMessageFormType');
323 370
         $definition->addTag('form.type', array('alias' => 'acme_friend_message'));
324 371
         $container->setDefinition(
325 372
             'acme.form.friend_message',
@@ -327,39 +374,44 @@ configuration.
327 374
             array('security.context')
328 375
         );
329 376
 
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 377
 If you wish to create it from within a controller or any other service that has
337 378
 access to the form factory, you then use::
338 379
 
339  
-    // src/AcmeDemoBundle/Controller/FriendMessageController.php
340  
-    public function friendMessageAction()
  380
+    class FriendMessageController extends Controller
341 381
     {
342  
-        $form = $this->get('form.factory')->create('acme_friend_message');
343  
-        $form = $form->createView();
  382
+        public function newAction(Request $request)
  383
+        {
  384
+            $form = $this->createForm('acme_friend_message');
  385
+
  386
+            // ...
  387
+        }
  388
+    }
  389
+
  390
+You can also easily embed the form type into another form::
344 391
 
345  
-        return compact('form');
  392
+    // inside some other "form type" class
  393
+    public function buildForm(FormBuilderInterface $builder, array $options)
  394
+    {
  395
+        $builder->add('message', 'acme_friend_message');
346 396
     }
347 397
 
348  
-Dynamic generation for submitted forms
349  
-======================================
  398
+.. _cookbook-form-events-submitted-data:
  399
+
  400
+Dynamic generation for submitted Forms
  401
+--------------------------------------
350 402
 
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
  403
+Another case that can appear is that you want to customize the form specific to
  404
+the data that was submitted by the user. For example, imagine you have a registration
353 405
 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
  406
+position on the field. This would be a ``choice`` field for example. However the
355 407
 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
  408
+goalkeeper etc... Baseball will have a pitcher but will not have goalkeeper. You
357 409
 will need the correct options to be set in order for validation to pass.
358 410
 
359 411
 The meetup is passed as an entity hidden field to the form. So we can access each
360 412
 sport like this::
361 413
 
362  
-    // src/Acme/DemoBundle/FormType/SportMeetupType.php
  414
+    // src/Acme/DemoBundle/Form/Type/SportMeetupType.php
363 415
     class SportMeetupType extends AbstractType
364 416
     {
365 417
         public function buildForm(FormBuilderInterface $builder, array $options)
@@ -374,7 +426,11 @@ sport like this::
374 426
                 FormEvents::PRE_SET_DATA,
375 427
                 function(FormEvent $event) use($user, $factory){
376 428
                     $form = $event->getForm();
377  
-                    $event->getData()->getSport()->getAvailablePositions();
  429
+
  430
+                    // this would be your entity, i.e. SportMeetup
  431
+                    $data = $event->getData();
  432
+
  433
+                    $positions = $data->getSport()->getAvailablePositions();
378 434
 
379 435
                     // ... proceed with customizing the form based on available positions
380 436
                 }
@@ -382,35 +438,42 @@ sport like this::
382 438
         }
383 439
     }
384 440
 
  441
+When you're building this form to display to the user for the first time,
  442
+then this example works perfectly.
  443
+
  444
+However, things get more difficult when you handle the form submission. This
  445
+is be cause the ``PRE_SET_DATA`` event tells us the data that you're starting
  446
+with (e.g. an empty ``SportMeetup`` object), *not* the submitted data.
385 447
 
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.
  448
+On a form, we can usually listen to the following events:
388 449
 
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)``.
  450
+* ``PRE_SET_DATA``
  451
+* ``POST_SET_DATA``
  452
+* ``PRE_BIND``
  453
+* ``BIND``
  454
+* ``POST_BIND``
393 455
 
394  
-On a form, we can usually listen to the following events::
  456
+When listening to ``BIND`` and ``POST_BIND``, it's already "too late" to make
  457
+changes to the form. Fortunately, ``PRE_BIND`` is perfect for this. There
  458
+is, however, a big difference in what ``$event->getData()`` returns for each
  459
+of these events. Specifically, in ``PRE_BIND``, ``$event->getData()`` returns
  460
+the raw data submitted by the user.
395 461
 
396  
- * ``PRE_SET_DATA``
397  
- * ``POST_SET_DATA``
398  
- * ``PRE_BIND``
399  
- * ``BIND``
400  
- * ``POST_BIND``
  462
+This can be used to get the ``SportMeetup`` id and retrieve it from the database,
  463
+given you have a reference to the object manager (if using doctrine). In
  464
+the end, you have an event subscriber that listens to two different events,
  465
+requires some external services and customizes the form. In such a situation,
  466
+it's probably better to define this as a service rather than using an anonymouse
  467
+function as the event listener callback.
401 468
 
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.
  469
+The subscriber would now look like::
406 470
 
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.
  471
+    // src/Acme/DemoBundle/Form/EventListener/RegistrationSportListener.php
  472
+    namespace Acme\DemoBundle\Form\EventListener;
412 473
 
413  
-Our subscriber would now look like::
  474
+    use Symfony\Component\Form\FormFactoryInterface;
  475
+    use Doctrine\ORM\EntityManager;
  476
+    use Symfony\Component\Form\FormEvent;
414 477
 
415 478
     class RegistrationSportListener implements EventSubscriberInterface
416 479
     {
@@ -420,14 +483,14 @@ Our subscriber would now look like::
420 483
         private $factory;
421 484
 
422 485
         /**
423  
-         * @var DocumentManager
  486
+         * @var EntityManager
424 487
          */
425 488
         private $om;
426 489
 
427 490
         /**
428 491
          * @param factory FormFactoryInterface
429 492
          */
430  
-        public function __construct(FormFactoryInterface $factory, ObjectManager $om)
  493
+        public function __construct(FormFactoryInterface $factory, EntityManager $om)
431 494
         {
432 495
             $this->factory = $factory;
433 496
             $this->om = $om;
@@ -435,16 +498,16 @@ Our subscriber would now look like::
435 498
 
436 499
         public static function getSubscribedEvents()
437 500
         {
438  
-            return [
  501
+            return array(
439 502
                 FormEvents::PRE_BIND => 'preBind',
440 503
                 FormEvents::PRE_SET_DATA => 'preSetData',
441  
-            ];
  504
+            );
442 505
         }
443 506
 
444 507
         /**
445  
-         * @param event DataEvent
  508
+         * @param event FormEvent
446 509
          */
447  
-        public function preSetData(DataEvent $event)
  510
+        public function preSetData(FormEvent $event)
448 511
         {
449 512
             $meetup = $event->getData()->getMeetup();
450 513
 
@@ -454,19 +517,20 @@ Our subscriber would now look like::
454 517
             }
455 518
 
456 519
             $form = $event->getForm();
457  
-            $positions = $meetup->getSport()->getPostions();
  520
+            $positions = $meetup->getSport()->getPositions();
458 521
 
459 522
             $this->customizeForm($form, $positions);
460 523
         }
461 524
 
462  
-        public function preBind(DataEvent $event)
  525
+        public function preBind(FormEvent $event)
463 526
         {
464 527
             $data = $event->getData();
465 528
             $id = $data['event'];
466 529
             $meetup = $this->om
467  
-                        ->getRepository('Acme\SportBundle\Document\Event')
468  
-                        ->find($id);
469  
-            if($meetup === null){
  530
+                ->getRepository('AcmeDemoBundle:SportMeetup')
  531
+                ->find($id);
  532
+
  533
+            if ($meetup === null) {
470 534
                 $msg = 'The event %s could not be found for you registration';
471 535
                 throw new \Exception(sprintf($msg, $id));
472 536
             }
@@ -482,12 +546,12 @@ Our subscriber would now look like::
482 546
         }
483 547
     }
484 548
 
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
  549
+You can see that you need to listen on these two events and have different callbacks
  550
+only because in two different scenarios, the data that you can use is given in a
487 551
 different format. Other than that, this class always performs exactly the same
488 552
 things on a given form.
489 553
 
490  
-Now that we have this set up, we need to create our services:
  554
+Now that you have that setup, register your form and the listener as services:
491 555
 
492 556
 .. configuration-block::
493 557
 
@@ -495,23 +559,23 @@ Now that we have this set up, we need to create our services:
495 559
 
496 560
         # app/config/config.yml
497 561
         acme.form.sport_meetup:
498  
-            class: Acme\SportBundle\FormType\RegistrationType
  562
+            class: Acme\SportBundle\Form\Type\SportMeetupType
499 563
             arguments: [@acme.form.meetup_registration_listener]
500 564
             tags:
501 565
                 - { name: form.type, alias: acme_meetup_registration }
502 566
         acme.form.meetup_registration_listener
503  
-            class: Acme\SportBundle\Form\RegistrationSportListener
  567
+            class: Acme\SportBundle\Form\EventListener\RegistrationSportListener
504 568
             arguments: [@form.factory, @doctrine]
505 569
 
506 570
     .. code-block:: xml
507 571
 
508 572
         <!-- app/config/config.xml -->
509 573
         <services>
510  
-            <service id="acme.form.sport_meetup" class="Acme\SportBundle\FormType\RegistrationType">
  574
+            <service id="acme.form.sport_meetup" class="Acme\SportBundle\FormType\SportMeetupType">
511 575
                 <argument type="service" id="acme.form.meetup_registration_listener" />
512 576
                 <tag name="form.type" alias="acme_meetup_registration" />
513 577
             </service>
514  
-            <service id="acme.form.meetup_registration_listener" class="Acme\SportBundle\Form\RegistrationSportListener">
  578
+            <service id="acme.form.meetup_registration_listener" class="Acme\SportBundle\Form\EventListener\RegistrationSportListener">
515 579
                 <argument type="service" id="form.factory" />
516 580
                 <argument type="service" id="doctrine" />
517 581
             </service>
@@ -520,24 +584,44 @@ Now that we have this set up, we need to create our services:
520 584
     .. code-block:: php
521 585
 
522 586
         // app/config/config.php
523  
-        $definition = new Definition('Acme\SportBundle\FormType\RegistrationType');
  587
+        $definition = new Definition('Acme\SportBundle\Form\Type\SportMeetupType');
524 588
         $definition->addTag('form.type', array('alias' => 'acme_meetup_registration'));
525 589
         $container->setDefinition(
526 590
             'acme.form.meetup_registration_listener',
527 591
             $definition,
528 592
             array('security.context')
529 593
         );
530  
-        $definition = new Definition('Acme\SportBundle\Form\RegistrationSportListener');
  594
+        $definition = new Definition('Acme\SportBundle\Form\EventListener\RegistrationSportListener');
531 595
         $container->setDefinition(
532 596
             'acme.form.meetup_registration_listener',
533 597
             $definition,
534 598
             array('form.factory', 'doctrine')
535 599
         );
536 600
 
537  
-And this should tie everything together. We can now retrieve our form from the
  601
+In this setup, the ``RegistrationSportListener`` will be a constructor argument
  602
+to ``SportMeetupType``. You can then register it as an event subscriber on
  603
+your form::
  604
+
  605
+    private $registrationSportListener;
  606
+
  607
+    public function __construct(RegistrationSportListener $registrationSportListener)
  608
+    {
  609
+        $this->registrationSportListener = $registrationSportListener;
  610
+    }
  611
+
  612
+    public function buildForm(FormBuilderInterface $builder, array $options)
  613
+    {
  614
+        // ...
  615
+        $subscriber = new AddNameFieldSubscriber($builder->getFormFactory());
  616
+        $builder->addEventSubscriber($this->registrationSportListener);
  617
+    }
  618
+
  619
+And this should tie everything together. You can now retrieve your form from the
538 620
 controller, display it to a user, and validate it with the right choice options
539 621
 set for every possible kind of sport that our users are registering for.
540 622
 
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
  623
+One piece that may still be missing is the client-side updating of your form
  624
+after the sport is selected. This should be handled by making an AJAX call
  625
+back to your application. In that controller, you can bind your form, but
  626
+instead of processing it, simply use the bound form to render the updated
  627
+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 0826e33

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