New in Symfony 8.2 (in development). A simple form used to require a separate class (or a createFormBuilder() in the middle of the controller) even though its fields closely follow the properties of the DTO, where the validation constraints are already declared with attributes. #[AsFormType] and #[FormField] bring the two together.
Before
$form = $this->createFormBuilder($message) ->add('name') ->add('email', EmailType::class) ->add('message', TextareaType::class, ['label' => 'Your message']) ->getForm();
Since Symfony 8.2
#[AsFormType] class MessageContact { #[FormField] #[Assert\NotBlank] public ?string $name = null; #[FormField(EmailType::class)] #[Assert\NotBlank] #[Assert\Email] public ?string $email = null; }
$form = $this->createForm(MessageContact::class, $message);
A class marked #[AsFormType] can be used anywhere Symfony expects a form type name. Its properties marked #[FormField] become its fields, in the order they are written: the type is guessed when it isn't given, and the second argument holds the field's options. The container reads these attributes at compile time: a mistake, such as two fields with the same name, shows up when the cache is cleared, not in the middle of a request.
The attributes describe fields, nothing more. A form that needs events, data transformers or dynamic fields keeps its form class, or gets a type extension.
Your task
Rewrite the contact form: the three fields are declared on MessageContact, and ContactController just asks for the form of that type. The /contact page must behave as before, “Your message” label included.