Skip to content

Commit ba0526a

Browse files
committed
Wrote article about Data Mappers
1 parent 9dbd4f5 commit ba0526a

File tree

3 files changed

+190
-1
lines changed

3 files changed

+190
-1
lines changed

form/data_mappers.rst

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
.. index::
2+
single: Form; Data mappers
3+
4+
How to Use Data Mappers
5+
=======================
6+
7+
Data mappers are the layer between your form data (e.g. the bound object) and
8+
the form. They are responsible for mapping the data to the fields and back. The
9+
built-in data mapper uses the :doc:`PropertyAccess component </components/property_access>`
10+
and will fit most cases. However, you can create your own data mapper that
11+
could, for example, pass data to immutable objects via their constructor.
12+
13+
The Difference between Data Transformers and Mappers
14+
----------------------------------------------------
15+
16+
It is important to know the difference between
17+
:doc:`data transformers </form/data_transformers>` and mappers.
18+
19+
* **Data transformers** change the representation of a value. E.g. from
20+
``"2016-08-12"`` to a ``DateTime`` instance;
21+
* **Data mappers** map data (e.g. an object) to form fields, and vice versa.
22+
23+
Changing a ``YYYY-mm-dd`` string value to a ``DateTime`` instance is done by a
24+
data transformer. Mapping this ``DateTime`` instance to a property on your
25+
object (e.g. by calling a setter or some other method) is done by a data
26+
mapper.
27+
28+
Creating a Data Mapper
29+
----------------------
30+
31+
Suppose that you want to save a set of colors to the database. For this, you're
32+
using an immutable color object::
33+
34+
// src/AppBundle/Colors/Color.php
35+
namespace AppBundle\Colors;
36+
37+
class Color
38+
{
39+
private $red;
40+
private $green;
41+
private $blue;
42+
43+
public function __construct($red, $green, $blue)
44+
{
45+
$this->red = $red;
46+
$this->green = $green;
47+
$this->blue = $blue;
48+
}
49+
50+
public function getRed()
51+
{
52+
return $this->red;
53+
}
54+
55+
public function getGreen()
56+
{
57+
return $this->green;
58+
}
59+
60+
public function getBlue()
61+
{
62+
return $this->blue;
63+
}
64+
}
65+
66+
The form type should be allowed to edit a color. But because you've decided to
67+
make the ``Color`` object immutable, a new color object has to be created each time
68+
one of the values is changed.
69+
70+
.. tip::
71+
72+
If you're using a mutable object with constructor arguments, instead of
73+
using a data mapper, you should configure the ``empty_data`` with a closure
74+
as described in
75+
:ref:`How to Configure empty Data for a Form Class <forms-empty-data-closure>`.
76+
77+
The red, green and blue form fields have to be mapped to the constructor
78+
arguments and the ``Color`` instance has to be mapped to red, green and blue
79+
form fields. Recognize a familiar pattern? It's time for a data mapper!
80+
81+
.. code-block:: php
82+
83+
// src/AppBundle/Form/DataMapper/ColorMapper.php
84+
namespace AppBundle\Form\DataMapper;
85+
86+
use AppBundle\Colors\Color;
87+
use Symfony\Component\Form\DataMapperInterface;
88+
use Symfony\Component\Form\Exception\UnexpectedTypeException;
89+
90+
class ColorMapper implements DataMapperInterface
91+
{
92+
public function mapDataToForms($data, $forms)
93+
{
94+
// there is no data yet, a new color will be created
95+
if (null === $data) {
96+
return;
97+
}
98+
99+
// invalid data type, this message will not be shown to the user (see below)
100+
if (!$data instanceof Color) {
101+
throw new UnexpectedTypeException($data, Color::class);
102+
}
103+
104+
$forms = iterator_to_array($forms);
105+
106+
// set form field values
107+
$forms['red']->setData($data->getRed());
108+
$forms['green']->setData($data->getGreen());
109+
$forms['blue']->setData($data->getBlue());
110+
}
111+
112+
public function mapFormsToData($forms, &$data)
113+
{
114+
$forms = iterator_to_array($forms);
115+
116+
// get form field values
117+
$red = $forms['red']->getData();
118+
$green = $forms['green']->getData();
119+
$blue = $forms['blue']->getData();
120+
121+
// as data is passed by reference, overriding it will change it in
122+
// the form object as well
123+
$data = new Color($red, $green, $blue);
124+
}
125+
}
126+
127+
.. caution::
128+
129+
The data passed to the mapper is *not yet validated*. This means that your
130+
objects should allow being created in an invalid state in order to produce
131+
user-friendly errors in the form.
132+
133+
Using the Mapper
134+
----------------
135+
136+
You're ready to use the data mapper for the ``ColorType`` form. Use the
137+
:method:`Symfony\\Component\\Form\\FormBuilderInterface::setDataMapper`
138+
method to configure the data mapper::
139+
140+
// src/AppBundle/Form/ColorType.php
141+
namespace AppBundle\Form;
142+
143+
use AppBundle\Form\DataMapper\ColorMapper;
144+
145+
// ...
146+
class ColorType extends AbstractType
147+
{
148+
public function buildForm(FormBuilderInterface $builder, array $options)
149+
{
150+
$builder
151+
->add('red', 'integer')
152+
->add('green', 'integer')
153+
->add('blue', 'integer')
154+
155+
->setDataMapper(new ColorMapper())
156+
;
157+
}
158+
159+
public function configureOptions(OptionsResolver $resolver)
160+
{
161+
$resolver->setDefaults(array(
162+
// when creating a new color, the initial data should be null
163+
'empty_data' => null,
164+
));
165+
}
166+
}
167+
168+
Cool! When using the ``ColorType`` form, the custom ``ColorMapper`` will create
169+
the ``Color`` object now.
170+
171+
.. caution::
172+
173+
When a form field has the ``inherit_data`` option set, data mappers won't
174+
be applied to that field.
175+
176+
.. tip::
177+
178+
You can also implement ``DataMapperInterface`` in the ``ColorType`` and add
179+
the ``mapDataToForms()`` and ``mapFormsToData()`` in the form type directly
180+
to avoid creating a new class. You'll then have to call
181+
``$builder->setDataMapper($this)``.

form/data_transformers.rst

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,15 @@ to render the form, and then back into a ``DateTime`` object on submit.
1313

1414
.. caution::
1515

16-
When a form field has the ``inherit_data`` option set, Data Transformers
16+
When a form field has the ``inherit_data`` option set, data transformers
1717
won't be applied to that field.
1818

19+
.. seealso::
20+
21+
If, instead of transforming the representation of a value, you need to map
22+
values to a form field and back, you should use a data mapper. Check out
23+
:doc:`/form/data_mappers`.
24+
1925
.. _simple-example-sanitizing-html-on-user-input:
2026

2127
Simple Example: Transforming String Tags from User Input to an Array

form/use_empty_data.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ some dependency into the ``BlogType`` when we instantiate it, then use that
7373
to instantiate the ``Blog`` class. The point is, you can set ``empty_data``
7474
to the exact "new" object that you want to use.
7575

76+
.. _forms-empty-data-closure:
77+
7678
Option 2: Provide a Closure
7779
---------------------------
7880

0 commit comments

Comments
 (0)