Symfony 8.2 New Published on

Describing a form with attributes

A form's fields are declared on the DTO, right next to the validation constraints: no more form class for simple cases.

Topics covered: FormsDTO

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.

Where the feature comes from: the original pull request.