When and How to Use Data Mappers

When a form is compound, the initial data needs to be passed to children so each can display their
own input value. On submission, children values need to be written back into the form.

Data mappers are responsible for reading and writing data from and into parent forms.

The main built-in data mapper uses the PropertyAccess component
and will fit most cases. However, you can create your own implementation that
could, for example, pass submitted data to immutable objects via their constructor.

Data transformers change the representation of a value (e.g. from
"2016-08-12" to a DateTime instance);

Data mappers map data (e.g. an object or array) to form fields, and vice versa.

Changing a YYYY-mm-dd string value to a DateTime instance is done by a
data transformer. Populating inner fields (e.g year, hour, etc) of a compound date type using
a DateTime instance is done by the data mapper.

The red, green and blue form fields have to be mapped to the constructor
arguments and the Color instance has to be mapped to red, green and blue
form fields. Recognize a familiar pattern? It's time for a data mapper. The
easiest way to create one is by implementing DataMapperInterface
in your form type:

// src/Form/ColorType.phpnamespaceApp\Form;useApp\Painting\Color;useSymfony\Component\Form\AbstractType;useSymfony\Component\Form\DataMapperInterface;useSymfony\Component\Form\Exception\UnexpectedTypeException;useSymfony\Component\Form\FormInterface;finalclassColorTypeextendsAbstractTypeimplementsDataMapperInterface{// .../** * @param Color|null $data */publicfunctionmapDataToForms($data,$forms){// there is no data yet, so nothing to prepopulateif(null===$data){return;}// invalid data typeif(!$datainstanceofColor){thrownewUnexpectedTypeException($data,Color::class);}/** @var FormInterface[] $forms */$forms=iterator_to_array($forms);// initialize form field values$forms['red']->setData($data->getRed());$forms['green']->setData($data->getGreen());$forms['blue']->setData($data->getBlue());}publicfunctionmapFormsToData($forms,&$data){/** @var FormInterface[] $forms */$forms=iterator_to_array($forms);// as data is passed by reference, overriding it will change it in// the form object as well// beware of type inconsistency, see caution below$data=newColor($forms['red']->getData(),$forms['green']->getData(),$forms['blue']->getData());}}

Caution

The data passed to the mapper is not yet validated. This means that your
objects should allow being created in an invalid state in order to produce
user-friendly errors in the form.

// src/Form/Type/ColorType.phpnamespaceApp\Form\Type;// ...useSymfony\Component\Form\Extension\Core\Type\IntegerType;useSymfony\Component\Form\FormBuilderInterface;useSymfony\Component\OptionsResolver\OptionsResolver;finalclassColorTypeextendsAbstractTypeimplementsDataMapperInterface{publicfunctionbuildForm(FormBuilderInterface$builder,array$options){$builder->add('red',IntegerType::class,[// enforce the strictness of the type to ensure the constructor// of the Color class doesn't break'empty_data'=>'0',])->add('green',IntegerType::class,['empty_data'=>'0',])->add('blue',IntegerType::class,['empty_data'=>'0',])// configure the data mapper for this FormType->setDataMapper($this);}publicfunctionconfigureOptions(OptionsResolver$resolver){// when creating a new color, the initial data should be null$resolver->setDefault('empty_data',null);}// ...}

Cool! When using the ColorType form, the custom data mapper methods will
create a new Color object now.

Caution

When a form has the inherit_data option set to true, it does not use the data mapper and
lets its parent map inner values.

Stateful Data Mappers

Sometimes, data mappers need to access services or need to maintain their
state. In this case, you cannot implement the methods in the form type
itself. Create a separate class implementing DataMapperInterface and
initialize it in your form type: