Skip to content

Recommend using form types as data mappers in the docs #11395

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 11, 2019
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 47 additions & 21 deletions form/data_mappers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Suppose that you want to save a set of colors to the database. For this, you're
using an immutable color object::

// src/AppBundle/Painting/Color.php
namespace App\Painting;
namespace AppBundle\Painting;

final class Color
{
Expand Down Expand Up @@ -78,18 +78,23 @@ one of the values is changed.

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::
form fields. Recognize a familiar pattern? It's time for a data mapper. The
easiest way to create one is by implementing :class:`Symfony\\Component\\Form\\DataMapperInterface`
in your form type::

// src/AppBundle/Form/DataMapper/ColorMapper.php
namespace App\Form\DataMapper;
// src/AppBundle/Form/ColorType.php
namespace AppBundle\Form;

use App\Painting\Color;
use AppBundle\Painting\Color;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\DataMapperInterface;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
use Symfony\Component\Form\FormInterface;

final class ColorMapper implements DataMapperInterface
final class ColorType extends AbstractType implements DataMapperInterface
{
// ...

/**
* @param Color|null $data
*/
Expand Down Expand Up @@ -139,20 +144,18 @@ form fields. Recognize a familiar pattern? It's time for a data mapper::
Using the Mapper
----------------

You're ready to use the data mapper for the ``ColorType`` form. Use the
:method:`Symfony\\Component\\Form\\FormConfigBuilderInterface::setDataMapper`
method to configure the data mapper::
After creating the data mapper, you need to configure the form to use it. This is
achieved using the :method:`Symfony\\Component\\Form\\FormConfigBuilderInterface::setDataMapper`
method::

// src/AppBundle/Form/Type/ColorType.php
namespace App\Form\Type;

use App\Form\DataMapper\ColorMapper;
use Symfony\Component\Form\AbstractType;
// ...
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

final class ColorType extends AbstractType
final class ColorType extends AbstractType implements DataMapperInterface
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
Expand All @@ -168,7 +171,8 @@ method to configure the data mapper::
->add('blue', IntegerType::class, [
'empty_data' => '0',
])
->setDataMapper(new ColorMapper())
// configure the data mapper for this FormType
->setDataMapper($this)
;
}

Expand All @@ -177,19 +181,41 @@ method to configure the data mapper::
// when creating a new color, the initial data should be null
$resolver->setDefault('empty_data', null);
}

// ...
}

Cool! When using the ``ColorType`` form, the custom ``ColorMapper`` will create
a new ``Color`` object now.
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.

.. tip::
.. sidebar:: Stateful Data Mappers

Sometimes, data mappers need to access services or need to maintain their
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sometimes, data mappers need to access services

This is actually wrong. When a data mapper needs a service, you can still implement DataMapperInterface in FormType and add the service to the constructor of the form type.

Indeed the only legit reason for creating a separate data mapper class if you want to use form options in the data mapper.

$form = $this->createForm(SomeType::class, $data, ['option_for_data_mapper' => 123]);

Since option values are specific to Form instances and not to the single FormType instance, we need multiple data mappers with different states here. Otherwise you can implement DataMapperInterface in FormType.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, thanks for clarifying. I was already wondering about the same thing.

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::

You can also implement the ``DataMapperInterface`` in the ``ColorType`` and add
the ``mapDataToForms()`` and ``mapFormsToData()`` in the form type directly
to avoid creating a new class. You'll then have to call
``$builder->setDataMapper($this)``.
// src/AppBundle/Form/Type/ColorType.php

// ...
use AppBundle\Form\DataMapper\ColorMapper;

final class ColorType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
// ...

// Initialize the data mapper class and e.g. pass some state
->setDataMapper(new ColorMapper($options['opacity']))
;
}

// ...
}