Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 78fc194

Browse files
committed
Merge pull request symfony#765 from mdpatrick/dynamic_form_generation
Dynamic form generation -- first shot
2 parents cbbe605 + 4184de5 commit 78fc194

File tree

4 files changed

+174
-0
lines changed

4 files changed

+174
-0
lines changed

book/forms.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1460,6 +1460,7 @@ Learn more from the Cookbook
14601460
* :doc:`File Field Reference </reference/forms/types/file>`
14611461
* :doc:`Creating Custom Field Types </cookbook/form/create_custom_field_type>`
14621462
* :doc:`/cookbook/form/form_customization`
1463+
* :doc:`/cookbook/form/dynamic_form_generation`
14631464

14641465
.. _`Symfony2 Form Component`: https://github.com/symfony/Form
14651466
.. _`DateTime`: http://php.net/manual/en/class.datetime.php

book/internals.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,8 @@ and set a new ``Exception`` object, or do nothing:
380380
.. index::
381381
single: Event Dispatcher
382382

383+
.. _`book-internals-event-dispatcher`:
384+
383385
The Event Dispatcher
384386
--------------------
385387

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
How to Dynamically Generate Forms Using Form Events
2+
===================================================
3+
4+
Before jumping right into dynamic form generation, let's have a quick review
5+
of what a form class boiled down to its most bare essentials looks like:
6+
7+
.. code-block:: php
8+
9+
//src/Acme/DemoBundle/Form/ProductType.php
10+
namespace Acme\DemoBundle\Form
11+
12+
use Symfony\Component\Form\AbstractType
13+
use Symfony\Component\Form\FormBuilder;
14+
15+
class ProductType extends AbstractType
16+
{
17+
public function buildForm(FormBuilder $builder, array $options)
18+
{
19+
$builder->add('name');
20+
$builder->add('price');
21+
}
22+
23+
public function getName()
24+
{
25+
return 'product';
26+
}
27+
}
28+
29+
.. note::
30+
31+
If this particular section of code isn't already familiar to you, you
32+
probably need to take a step back and first review the :doc:`Forms chapter </book/forms>`
33+
before proceeding.
34+
35+
Let's assume for a moment that this form utilizes an imaginary "Product" entity
36+
that has only two relevant properties ("name" and "price"). The form generated
37+
from this class will look the exact same if it is holding a Product object
38+
that is brand new, and has not had any of its properties set, as when it
39+
is being used to alter or update an existing record in the database that has
40+
been fetched with Doctrine.
41+
42+
Suppose now, that you don't want the user to be able to change the `name` value
43+
once the object has been created. To do this, you can rely on Symfony's :ref:`Event Dispatcher <book-internals-event-dispatcher>`
44+
system to analyze the data on the object and modify the form based on the
45+
Product object's data. In this entry, you'll learn how to add this level of
46+
flexibility to your forms.
47+
48+
.. _`cookbook-forms-event-subscriber`:
49+
50+
Adding An Event Subscriber To A Form Class
51+
------------------------------------------
52+
53+
So, instead of directly adding that "name" widget via our ProductType form
54+
class, let's delegate the responsibility of creating that particular widget
55+
to an Event Subscriber
56+
57+
.. code-block:: php
58+
59+
//src/Acme/DemoBundle/Form/ProductType.php
60+
namespace Acme\DemoBundle\Form
61+
62+
use Symfony\Component\Form\AbstractType
63+
use Symfony\Component\Form\FormBuilder;
64+
use Acme\DemoBundle\Form\EventListener\MyFormListener;
65+
66+
class ProductType extends AbstractType
67+
{
68+
public function buildForm(FormBuilder $builder, array $options)
69+
{
70+
$listener = new MyFormListener($builder->getFormFactory());
71+
$builder->addEventSubscriber($listener);
72+
$builder->add('price');
73+
}
74+
75+
public function getName()
76+
{
77+
return 'product';
78+
}
79+
}
80+
81+
The listener is passed the FormFactory object in its constructor so that our
82+
new listener class is capable of creating the form widget once it is notified
83+
of the dispatched event during form creation.
84+
85+
.. _`cookbook-forms-listener-class`:
86+
87+
Inside of The Listener Class
88+
----------------------------
89+
90+
Based on our previous scenario where our form creates a widget for the "name"
91+
property if and only if it is passed an object that has never been persisted
92+
to the database (and thus has a blank "id" property) our listener might look
93+
look like the following:
94+
95+
.. code-block:: php
96+
97+
// src/Acme/DemoBundle/Form/EventListener/MyFormListener.php
98+
namespace Acme\DemoBundle\Form\EventListener;
99+
100+
use Symfony\Component\Form\Event\DataEvent;
101+
use Symfony\Component\Form\FormFactoryInterface;
102+
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
103+
use Symfony\Component\Form\FormEvents;
104+
105+
class MyFormListener implements EventSubscriberInterface
106+
{
107+
private $factory;
108+
109+
public function __construct(FormFactoryInterface $factory)
110+
{
111+
$this->factory = $factory;
112+
}
113+
114+
public static function getSubscribedEvents()
115+
{
116+
// Tells the dispatcher to pass form.pre_set_data's event object
117+
// to our event subscriber via the preSetData() method.
118+
return array(FormEvents::PRE_SET_DATA => 'preSetData');
119+
}
120+
121+
public function preSetData(DataEvent $event)
122+
{
123+
$data = $event->getData();
124+
$form = $event->getForm();
125+
126+
// During form creation setData() is called with null as an argument
127+
// by the FormBuilder constructor. We're only concerned with when
128+
// setData is called with an actual Entity object in it (whether new,
129+
// or fetched with Doctrine). This if statement let's us skip right
130+
// over the null condition.
131+
if (null === $data) {
132+
return;
133+
}
134+
135+
if ($data->getId()) {
136+
$form->add($this->factory->createNamed('text', 'name'));
137+
}
138+
}
139+
140+
}
141+
142+
.. caution::
143+
144+
It is easy to misunderstand the purpose of the ``if ($data == null)`` segment
145+
of this event subscriber. To fully understand its role, you might consider
146+
also taking a look at the `Form class`_ and paying special attention to
147+
where setData() is called at the end of the constructor, as well as the
148+
setData() method itself.
149+
150+
The ``FormEvents::PRE_SET_DATA`` line actually resolves to ``form.pre_set_data``.
151+
The FormEvents class serves an organizational purpose. It is a centralized
152+
location in which you can find all of the various form events available.
153+
154+
While this example could have used the ``form.set_data`` or even the ``form.post_set_data``
155+
events just as effectively, by using ``form.pre_set_data`` we guarantee that
156+
the data being retrieved from the ``Event`` object has in no way been modified
157+
by any other subscribers or listeners. This is because ``form.pre_set_data``
158+
passes a `DataEvent`_ object instead of the `FilterDataEvent`_ object passed
159+
by the ``form.set_data`` event. `DataEvent`_, unlike its child `FilterDataEvent`_,
160+
lacks a setData() method.
161+
162+
.. note::
163+
164+
You may view the full list of form events via the `FormEvents class`_,
165+
found in the form bundle.
166+
167+
.. _`DataEvent`: https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/Event/DataEvent.php
168+
.. _`FormEvents class`: https://github.com/symfony/Form/blob/master/FormEvents.php
169+
.. _`Form class`: https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/Form.php
170+
.. _`FilterDataEvent`: https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/Event/FilterDataEvent.php

cookbook/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ Cookbook
2828
form/form_customization
2929
form/create_custom_field_type
3030
validation/custom_constraint
31+
form/dynamic_form_generation
3132

3233
configuration/environments
3334
configuration/external_parameters

0 commit comments

Comments
 (0)