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