В Symfony Forms появились свойства «getter» и «setter» для полей формы. Нет больше картографа данных, больше нет ограничений!

Интеграция сущностей Doctrine и форм Symfony в Symfony v5.2 стала намного проще благодаря свойствам «getter» и «setter» полей формы.

Сопоставитель данных по умолчанию компонента формы использует компонент PropertyAccess. В результате для изменения свойства в сущности через форму требовались простые методы getter и setter:

private ?string $email;

public function getEmail(): ?string
{
    return $this->email;
}

public function setEmail(?string $email): void
{
    $this->email = $email;
}

Это было относительно легко и дешево. Однако вместо того, чтобы иметь осмысленный домен, наши объекты были сведены к просто Объектам передачи данных — бессмысленным и не имеющим никакого поведения. В конечном итоге это привело к антипаттерну под названием Модель анемичной предметной области.

С этим подходом возникла дополнительная проблема. Объект домена должен был временно (но все же!) находиться в недопустимом состоянии. Правило Domain Driven Design заключается в том, что объекты вашего домена должны всегда находиться в действительном состоянии.

При этом у нас было несколько вариантов, чтобы сделать все правильно. Либо переключитесь на настоящие Объекты передачи данных и:



или создать собственный объект Data Mapper, способный заполнять поля формы из объекта и передавать значения полей формы обратно в сущность.

Начиная с версии 5.2 в Symfony Forms все стало намного проще.

На этот раз давайте создадим более значимый объект домена, чтобы запретить недопустимое состояние объекта и убедиться, что свойство не превышает длину поля базы данных:

private string $email;

public function getEmail(): string
{
    return $this->email;
}

/** @throws AssertionFailedException */
public function changeEmail(string $email): void
{
    Assertion::notBlank($email);
    Assertion::maxLength($email, 255);
    Assertion::email($email);

    $this->mail = $email;
}

Пример класса формы Symfony:

final class ChangeEmailForm extends AbstractType
{
    /** @inheritDoc */
    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => User::class,
        ]);
    }

    /** @inheritDoc */
    public function buildForm(
        FormBuilderInterface $builder,
        array $options
    ): void {
        $builder
            ->add('email', EmailType::class, [
                'label' => 'Email',
                'getter' => function (User $user): string {
                    return $user->getEmail();
                },
                'setter' => function (
                    User $user,
                    ?string $value,
                    FormInterface $form
                ): void {
                    try {
                        $user->changeEmail((string) $value);
                    } catch (AssertionFailedException $exception) {
                        $form->addError(
                            new UserError($exception->getMessage()
                        );
                    } 
                },
            ]);
    }
}

Также становится довольно забавно использовать встроенные формы. Как насчет воображаемой сущности статьи для совершенно новой системы CMS:

final class PostArticleForm extends AbstractType
{
    /** @inheritDoc */
    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => Article::class,
        ]);
    }

    /** @inheritDoc */
    public function buildForm(
        FormBuilderInterface $builder,
        array $options
    ): void {
        $builder
            ->add('content', ArticleContentType::class, [
                'getter' => function (Article $article): array {
                    return [
                        'title' => $article->getTitle(),
                        'content' => $article->getContent(),
                    ];
                },
                'setter' => function (
                    Article $article,
                    array $values,
                    FormInterface $form
                ): void {
                    try {
                        $article->createContentRevision(
                            $values['title'] ?? '',
                            $values['content'] ?? '',
                        );
                    } catch (AssertionFailedException $exception) {
                        $form->addError(
                            new UserError($exception->getMessage()
                        );
                    } 
                },
            ]);
    }
}

Мне очень нравится этот новый подход, поскольку он значительно упростил работу с формами и объектами! Не говоря уже о том, что этой функции уже 2 года:



Короткий вопрос: была ли эта история для вас важной?

Пожалуйста, поддержите мою работу, оставив аплодисменты в знак признательности. Приятно время от времени видеть такое уведомление в мобильном приложении. Спасибо!