|
| 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)``. |
0 commit comments