跳到内容

数据映射器的使用时间和方法

编辑此页

当表单是复合表单时,初始数据需要传递给子表单,以便每个子表单都能显示自己的输入值。提交时,子表单的值需要写回到父表单中。

数据映射器负责从父表单读取数据并写入数据。

主要的内置数据映射器使用 PropertyAccess 组件,并且适用于大多数情况。但是,您可以创建自己的实现,例如,可以通过构造函数将提交的数据传递给不可变对象。

数据转换器和映射器之间的区别

重要的是要了解 数据转换器 和映射器之间的区别。

  • 数据转换器 更改单个值的表示形式,例如,从 "2016-08-12" 更改为 DateTime 实例;
  • 数据映射器 将数据(例如,对象或数组)映射到一个或多个表单字段,反之亦然,例如,使用单个 DateTime 实例来填充复合日期类型的内部字段(例如,年、小时等)。

创建数据映射器

假设您想要将一组颜色保存到数据库中。为此,您正在使用一个不可变的颜色对象

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
// src/Painting/Color.php
namespace App\Painting;

final class Color
{
    public function __construct(
        private int $red,
        private int $green,
        private int $blue,
    ) {
    }

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

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

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

表单类型应允许编辑颜色。但是,由于您已决定使 Color 对象不可变,因此每次更改其中一个值时都必须创建一个新的颜色对象。

提示

如果您正在使用带有构造函数参数的可变对象,则应该使用 empty_data 选项和一个闭包进行配置,而不是使用数据映射器,如 如何为表单类配置空数据 中所述。

红色、绿色和蓝色表单字段必须映射到构造函数参数,并且 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()
        );
    }
}

警告

传递给映射器的数据尚未经过验证。这意味着您的对象应允许在无效状态下创建,以便在表单中生成用户友好的错误。

使用映射器

创建数据映射器后,您需要配置表单以使用它。这可以通过使用 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);
    }

    // ...
}

太棒了!当使用 ColorType 表单时,自定义数据映射器方法现在将创建一个新的 Color 对象。

使用回调映射表单字段

方便的是,您还可以通过使用 gettersetter 选项来映射表单字段的数据。例如,假设您有一个包含一些字段的表单,其中只有一个字段需要以某种特殊方式映射,或者您只需要更改它写入底层对象的方式。在这种情况下,注册一个 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): void
{
    // ...

    $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();
            }
        },
    ]);
}

如果可用,这些选项的优先级高于属性路径访问器,并且默认数据映射器仍将对其他表单字段使用 PropertyAccess 组件

警告

当表单的 inherit_data 选项设置为 true 时,它不使用数据映射器,而是让其父表单映射内部值。

本作品,包括代码示例,根据 Creative Commons BY-SA 3.0 许可获得许可。
目录
    版本