Как и когда использовать отображатели данных
Дата обновления перевода 2023-09-04
Как и когда использовать отображатели данных
Когда форма составная, изначальные данные должны быть переданы дочерним, чтобы каждая могла отображать собственное значение ввода. При отправке, значения дочерних форм должны быть записаны обратно в форму.
Отображатели данных отвечают за чтение и запись данных из/в родительскую форму.
Основной встроенный отображатель данных использует компонент 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
постоянным, новый объект цвета должен быть создан каждый раз,
когда изменяетя одно из значений.
Tip
Если вы используете изменяемые объект с аргументами конструктора, вместо
использования отображателя данных, вам нужно сконфигурировать опцию 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
{
// здесь еще нет данных, поэтому нечего предварительно наполнять
if (null === $viewData) {
return;
}
// невалидный тип данных
if (!$viewData instanceof Color) {
throw new UnexpectedTypeException($viewData, Color::class);
}
/** @var FormInterface[] $forms */
$forms = iterator_to_array($forms);
// инициализировать значения полей формы
$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);
// так как данные передаются по ссылке, переопределение изменит их и в
// объекте формы
// не забывайте о несовместимости типов, см. предупреждение ниже
$viewData = new Color(
$forms['red']->getData(),
$forms['green']->getData(),
$forms['blue']->getData()
);
}
}
Caution
Данные, переданные отображателю, еще не валидированы. Это означает, что ваши объекты должны позволять свое создание в невалидном состоянии, чтобы предоставить дружелюбные по отношению к пользователю ошибки в форме.
Использование отображателя
После создания отображателя данных, вам нужно сконфигурировать форму для его использования. Этого можно достичь используя метод 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, [
// форсировать строгость типа, чтобы гарантировать, что конструктор
// класса Color не сломается
'empty_data' => '0',
])
->add('green', IntegerType::class, [
'empty_data' => '0',
])
->add('blue', IntegerType::class, [
'empty_data' => '0',
])
// сконфигурировать отображатель данных для этого FormType
->setDataMapper($this)
;
}
public function configureOptions(OptionsResolver $resolver): void
{
// при создании нового цвета, изначальные данные должны быть null
$resolver->setDefault('empty_data', null);
}
// ...
}
Круто! При использовании формы ColorType
, пользовательские методы отображателя
данных теперь создадут новый объект Color
.
Отображение полей формы с использованием обратных вызовов
Удобно, что вы также можете отображать данные из и в поле формы, используя
опции getter
и setter
. Например, представьте, что у вас есть форма
с некоторыми полями, и только одно из них должно быть отображено каким-то особым
образом. Или вам нужно изменить то, как оно записывается в подлежащий объект.
В таком случае, зарегистрируйте 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();
}
},
]);
}
Если они доступны, эти опции имеют главенство над свойством метода пути и отображатель данных по умолчанию все еще будет использовать компонент PropertyAccess для других полей формы.
Caution
Когда форма имеет опцию inherit_data
установленную, как true
, она
не использует отображатель данных и позволяет родительской форме отображать
внутренние значения.