В 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 года:
Короткий вопрос: была ли эта история для вас важной?
Пожалуйста, поддержите мою работу, оставив аплодисменты в знак признательности. Приятно время от времени видеть такое уведомление в мобильном приложении. Спасибо!