8000 Wrote article about Data Mappers · symfony/symfony-docs@bc61395 · GitHub
[go: up one dir, main page]

Skip to content

Commit bc61395

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

File tree

3 files changed

+206
-1
lines changed

3 files changed

+206
-1
lines changed

form/data_mappers.rst

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
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 for the
11+
other cases, for instance when dealing with immutable objects.
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.
22+
23+
Changing a ``YYYY-mm-dd`` string value to a ``DateTime`` instance is done by a
24+
data transformer. Mapping this ``DateTime`` instance as value of a property on
25+
the entity is done by a data mapper.
26+
27+
Creating a Data Mapper
28+
----------------------
29+
30+
Assume you're saving a set of colors in the database. For this, you're using an
31+
immutable color object::
32+
33+
// src/AppBundle/Colors/Color.php
34+
namespace AppBundle\Colors;
35+
36+
class Color
37+
{
38+
private $red;
39+
private $green;
40+
private $blue;
41+
42+
public function __construct($red, $green, $blue)
43+
{
44+
$this->red = $red;
45+
$this->green = $green;
46+
$this->blue = $blue;
47+
}
48+
49+
public function getRed()
50+
{
51+
return $this->red;
52+
}
53+
54+
public function getGreen()
55+
{
56+
return $this->green;
57+
}
58+
59+
public function getBlue()
60+
{
61+
return $this->blue;
62+
}
63+
}
64+
65+
The form type should be allowed to edit a color. As the color object is
66+
immutable, a new color object has to be created each time one of the values is
67+
changed.
68+
69+
.. tip::
70+
71+
If you're using a mutable object with constructor arguments, instead of
72+
using a data mapper, you should configure the ``empty_data`` with a closure
73+
as described in
74+
:ref:`How to Configure empty Data for a Form Class <forms-empty-data-closure>`.
75+
76+
The red, green and blue form fields have to be mapped to the constructor
77+
arguments and the ``Color`` instance has to be mapped to red, green and blue
78+
form fields. Recognize a familair pattern? It's time for a data mapper!
79+
80+
.. code-block:: php
81+
82+
// src/AppBundle/Form/DataMapper/ColorMapper.php
83+
namespace AppBundle\Form\DataMapper;
84+
85+
use AppBundle\Colors\Color;
86+
use Symfony\Component\Form\DataMapperInterface;
87+
use Symfony\Component\Form\Exception\UnexpectedTypeException;
88+
89+
class ColorMapper extends DataMapperInterface
90+
{
91+
public function mapDataToForms($data, $forms)
92+
{
93+
// there is no data yet, a new color will be created
94+
if (null === $data) {
95+
return;
96+
}
97+
98+
// invalid data type, this message will not be shown to the user (see below)
99+
if (!$data instanceof Color) {
100+
throw new UnexpectedTypeException($data, Color::class);
101+
}
102+
103+
$forms = iterator_to_array($forms);
104+
105+
// set form field values
106+
$forms['red']->setData($data->getRed());
107+
$forms['green']->setData($data->getGreen());
108+
$forms['blue']->setData($data->getBlue());
109+
}
110+
111+
public function mapFormsToData($forms, &$data)
112+
{
113+
$forms = iterator_to_array($forms);
114+
115+
// get form field values
116+
$red = $forms['red']->getData();
117+
$green = $forms['green']->getData();
118+
$blue = $forms['blue']->getData();
119+
120+
$data = new Color($red, $green, $blue);
121+
}
122+
}
123+
124+
All data mappers have to implement :class:`Symfony\\Component\\Form\\DataMapperInterface`.
125+
This interface contains two methods:
126+
127+
``mapDataToForms()``
128+
Recieves the current view data and a :phpclass:`RecursiveIteratorIterator`
129+
with all form fields. It's task is to map the view data to the form fields.
130+
The view data might be ``null`` if there is no data yet (e.g. in a "Create
131+
new color" form);
132+
133+
``mapFormsToData($forms, &$data)``
134+
Maps the form field values to the ``$data`` property, which is changed by
135+
reference. It recieves the form fields in a :phpclass:`RecursiveIteratorIterator`
136+
and the view data.
137+
138+
When an errors occurs, a
139+
:class:`Symfony\\Component\\Form\\Exception\\TransformationFailedException` or
140+
one of its subclasses should be thrown. The exception message won't be shown to
141+
the user. Instead, the ``invalid_message`` option will be used.
142+
143+
.. caution::
144+
145+
The data passed to the mapper is *not yet validated*. This means that your
146+
objects should allow being created in an invalid state in order to produce
147+
user-friendly errors in the form.
148+
149+
Using the Mapper
150+
----------------
151+
152+
You're ready to use the data mapper for the ``ColorType`` form. Use the
153+
:method:`Symfony\\Component\\Form\\FormBuilderInterface::setDataMapper`
154+
method to configure the data mapper::
155+
156+
// src/AppBundle/Form/ColorType.php
157+
namespace AppBundle\Form;
158+
159+
use AppBundle\Form\DataMapper\ColorMapper;
160+
161+
// ...
162+
class ColorType extends AbstractType
163+
{
164+
public function buildForm(FormBuilderInterface $builder, array $options)
165+
{
166+
$builder
167+
->add('red', 'integer')
168+
->add('green', 'integer')
169+
->add('blue', 'integer')
170+
171+
->setDataMapper(new ColorMapper())
172+
;
173+
}
174+
175+
public function configureOptions(OptionsResolver $resolver)
176+
{
177+
$resolver->setDefaults(array(
178+
// when creating a new color, the initial data should be null
179+
'empty_data' => null,
180+
));
181+
}
182+
}
183+
184+
Cool! When using the ``ColorType`` form, the custom ``ColorMapper`` will create
185+
the ``Color`` object now.
186+
187+
.. caution::
188+
189+
When a form field has the ``inherit_data`` option set, data mappers won't
190+
be applied to that field.
191+
192+
.. tip::
193+
194+
You can also implement ``DataMapperInterface`` in the ``ColorType`` and add
195+
the ``mapDataToForms()`` and ``mapFormsToData()`` in the form type directly
196+
to avoid creating a new class. You'll then have to call
197+
``$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)
0