Як і коли використовувати відображувачі даних

Дата оновлення перекладу 2022-12-14

Як і коли використовувати відображувачі даних

Коли форма сполучена, початкові дані повинні бути передані дочірнім, щоб кожна могла відображати власне значення введення. При відправці, значення дочірніх форм повинні бути записані назад у форму.

Відображувачі даних відповідають за читання та запис даних з/у батьківську форму.

Основний вбудований відображувач даних використовує компонент 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
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;
    }
}

Тип форми повинен мати дозвіл на зміну кольору. Але так як ви вирішили зробити обʼєкь 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, [
                // форсуйте жорсткість типу, щоб гарантувати, що конструктор
                // класу Кольору не зламається
                '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, вона не використовує відображувач даних та дозволяє батьківській формі відображувати внутрішні значення.