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.

メインの組み込みデータ マッパーは PropertyAccess コンポーネントを使用し、ほとんどの場合に適合します。ただし、たとえば、送信されたデータをコンストラクターを介して不変オブジェクトに渡すことができる独自の実装を作成できます。

The Difference between Data Transformers and Mappers

It is important to know the difference between data transformers and mappers.

データ トランスフォーマーとマッパーの違いを理解することは重要です。
  • Data transformers change the representation of a single value, e.g. from "2016-08-12" to a DateTime instance;
    データ トランスフォーマーは、単一の値の表現を変更します。 "2016-08-12" から DateTime インスタンスへ。
  • Data mappers map data (e.g. an object or array) to one or many form fields, and vice versa, e.g. using a single DateTime instance to populate the inner fields (e.g year, hour, etc.) of a compound date type.
    データ マッパーは、データ (オブジェクトや配列など) を 1 つまたは複数のフォーム フィールドにマップします。単一の DateTime インスタンスを使用して、複合日付型の内部フィールド (年、時間など) を入力します。

Creating a Data Mapper

Suppose that you want to save a set of colors to the database. For this, you're using an immutable color object:

色のセットをデータベースに保存するとします。このために、不変の色オブジェクトを使用しています。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// src/Painting/Color.php
namespace App\Painting;

final class Color
{
    private $red;
    private $green;
    private $blue;

    public function __construct(int $red, int $green, int $blue)
    {
        $this->red = $red;
        $this->green = $green;
        $this->blue = $blue;
    }

    public function getRed(): int
    {
        return $this->red;
    }

    public function getGreen(): int
    {
        return $this->green;
    }

    public function getBlue(): int
    {
        return $this->blue;
    }
}

The form type should be allowed to edit a color. But because you've decided to make the Color object immutable, a new color object has to be created each time one of the values is changed.

フォーム タイプは、色の編集を許可する必要があります。ただし、Color オブジェクトを不変にすることにしたので、値の 1 つが変更されるたびに新しい色オブジェクトを作成する必要があります。

Tip

ヒント

If you're using a mutable object with constructor arguments, instead of using a data mapper, you should configure the empty_data option with a closure as described in How to Configure empty Data for a Form Class.

データマッパーを使用する代わりに、コンストラクター引数で可変オブジェクトを使用している場合は、フォームクラスの空のデータを構成する方法で説明されているように、クロージャーを使用して empty_data オプションを構成する必要があります。

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:

赤、緑、青のフォーム フィールドをコンストラクタ引数にマッピングし、Color インスタンスを赤、緑、青のフォーム フィールドにマッピングする必要があります。おなじみのパターンを認識していますか?データマッパーの出番です。これを作成する最も簡単な方法は、フォーム タイプに DataMapperInterface を実装することです。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// src/Form/ColorType.php
namespace App\Form;

use App\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 ColorType extends AbstractType implements DataMapperInterface
{
    // ...

    /**
     * @param Color|null $viewData
     */
    public function mapDataToForms($viewData, \Traversable $forms): void
    {
        // there is no data yet, so nothing to prepopulate
        if (null === $viewData) {
            return;
        }

        // invalid data type
        if (!$viewData instanceof Color) {
            throw new UnexpectedTypeException($viewData, Color::class);
        }

        /** @var FormInterface[] $forms */
        $forms = iterator_to_array($forms);

        // initialize form field values
        $forms['red']->setData($viewData->getRed());
        $forms['green']->setData($viewData->getGreen());
        $forms['blue']->setData($viewData->getBlue());
    }

    public function mapFormsToData(\Traversable $forms, &$viewData): void
    {
        /** @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
        $viewData = new Color(
            $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.

マッパーに渡されたデータはまだ検証されていません。これは、フォームでユーザーフレンドリーなエラーを生成するために、オブジェクトが無効な状態で作成されることを許可する必要があることを意味します。

Using the Mapper

After creating the data mapper, you need to configure the form to use it. This is achieved using the setDataMapper() method:

データ マッパーを作成したら、それを使用するようにフォームを構成する必要があります。これは setDataMapper() メソッドを使用して達成されます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// src/Form/Type/ColorType.php
namespace App\Form\Type;

// ...
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

final class ColorType extends AbstractType implements DataMapperInterface
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $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)
        ;
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        // 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.

涼しい! ColorType フォームを使用する場合、カスタム データ マッパー メソッドは新しい Color オブジェクトを作成します。

Mapping Form Fields Using Callbacks

Conveniently, you can also map data from and into a form field by using the getter and setter options. For example, suppose you have a form with some fields and only one of them needs to be mapped in some special way or you only need to change how it's written into the underlying object. In that case, register a PHP callable that is able to write or read to/from that specific object:

便利なことに、getter オプションと setter オプションを使用して、フォーム フィールドとの間でデータをマップすることもできます。たとえば、いくつかのフィールドを持つフォームがあり、そのうちの 1 つだけを特別な方法でマップする必要があるか、または基になるオブジェクトへの書き込み方法を変更するだけでよいとします。その場合、その特定のオブジェクトに対して書き込みまたは読み取りが可能な PHP 呼び出し可能オブジェクトを登録します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public function buildForm(FormBuilderInterface $builder, array $options)
{
    // ...

    $builder->add('state', ChoiceType::class, [
        'choices' => [
            'active' => true,
            'paused' => false,
        ],
        'getter' => function (Task $task, FormInterface $form): bool {
            return !$task->isCancelled() && !$task->isPaused();
        },
        'setter' => function (Task &$task, bool $state, FormInterface $form): void {
            if ($state) {
                $task->activate();
            } else {
                $task->pause();
            }
        },
    ]);
}

If available, these options have priority over the property path accessor and the default data mapper will still use the PropertyAccess component for the other form fields.

利用可能な場合、これらのオプションはプロパティ パス アクセサーよりも優先され、デフォルトのデータ マッパーは他のフォーム フィールドに引き続き PropertyAccess コンポーネントを使用します。

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.

フォームの inherit_data オプションが true に設定されている場合、フォームはデータ マッパーを使用せず、親に内部値をマップさせます。