Créer des formulaires est une tâche de base dans n’importe quelle application web. Vous galérez à comprendre la logique du composant Form de Symfony ? Vous cherchez un guide clair, sans blabla, qui vous montre exactement quoi faire ?
Cet article est un tutoriel complet qui vous explique comment créer un formulaire de A à Z avec Symfony 7, étape par étape. Vous apprendrez à générer le formulaire, le configurer, le gérer dans un contrôleur et l’afficher avec Twig.
Étape 1 : Générer le Formulaire avec la Console (`make:form`)
On pourrait créer le formulaire directement dans le contrôleur. Mais c’est une mauvaise pratique. La bonne méthode, c’est de créer une classe dédiée pour chaque formulaire. Ça s’appelle un Form Type. L’avantage est simple : votre formulaire est réutilisable partout dans votre projet et le code est mieux organisé. C’est ce qu’on appelle la séparation des préoccupations.
Pour créer cette classe, Symfony met à disposition une commande très pratique. Ouvrez votre terminal et tapez :
symfony console make:form
Le terminal va vous poser une question pour savoir comment nommer votre classe de formulaire. Pour notre exemple, on va créer un formulaire de contact. Appelons-le ContactType.
💡 Que fait cette commande ? Elle crée un fichier `src/Form/ContactType.php` avec toute la structure de base nécessaire. Vous n’avez plus qu’à le remplir.
Voici à quoi ressemble le fichier généré. C’est une simple classe PHP qui étend `AbstractType`. Elle contient deux méthodes principales : `buildForm` et `configureOptions`.
<?php
// src/Form/ContactType.php
namespace App\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ContactType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
// Les champs seront ajoutés ici
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
// Configurez vos options ici
]);
}
}
Étape 2 : Configurer les Champs du Formulaire (`FormBuilderInterface`)
Maintenant que notre classe `ContactType` est créée, il faut lui ajouter des champs. Tout se passe dans la méthode `buildForm`. Cette méthode reçoit un objet `$builder` de type `FormBuilderInterface`. C’est cet objet qui nous permet d’ajouter, de modifier ou de supprimer des champs.
Pour notre formulaire de contact, nous avons besoin de trois champs et d’un bouton d’envoi :
- Un champ pour le nom (type texte)
- Un champ pour l’email (type email)
- Un champ pour le message (type zone de texte)
- Un bouton pour soumettre le formulaire
Pour chaque champ, on utilise la méthode `add()` du builder. On doit spécifier le nom du champ et son type. Les types de champs sont des classes que Symfony fournit. Il faut penser à les importer en haut du fichier avec `use`.
Modifions le fichier `src/Form/ContactType.php` pour ajouter nos champs :
<?php
// src/Form/ContactType.php
namespace App\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ContactType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('nom', TextType::class, [
'label' => 'Votre nom',
'attr' => [
'placeholder' => 'Entrez votre nom complet'
]
])
->add('email', EmailType::class, [
'label' => 'Votre adresse e-mail',
'attr' => [
'placeholder' => 'exemple@domaine.com'
]
])
->add('message', TextareaType::class, [
'label' => 'Votre message',
'attr' => [
'rows' => 5
]
])
->add('envoyer', SubmitType::class, [
'label' => 'Envoyer le message'
])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([]);
}
}
💡 Options des champs : Le troisième argument de la méthode `add()` est un tableau d’options. `label` permet de définir le texte affiché à côté du champ, et `attr` permet d’ajouter des attributs HTML comme `placeholder` ou `class`.
Étape 3 : Créer le Contrôleur pour Gérer le Formulaire
Le formulaire est prêt, mais il n’est visible nulle part. Il faut maintenant créer une page et un contrôleur pour l’afficher et gérer les données soumises. On va créer un `ContactController` avec une méthode `index()`.
Dans ce contrôleur, on doit faire deux choses : créer une instance de notre formulaire et la passer à la vue (le template Twig). Pour créer l’instance, on utilise la méthode `$this->createForm()`. On lui donne en paramètre la classe de notre formulaire : `ContactType::class`.
On a aussi besoin d’injecter l’objet `Request` dans la méthode du contrôleur. Cet objet contient toutes les informations de la requête HTTP, y compris les données envoyées par l’utilisateur quand il soumet le formulaire. C’est l’objet `handleRequest` qui va lire ces informations et les lier au formulaire.
Voici le code du contrôleur `src/Controller/ContactController.php` :
<?php
// src/Controller/ContactController.php
namespace App\Controller;
use App\Form\ContactType;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class ContactController extends AbstractController
{
#[Route('/contact', name: 'app_contact')]
public function index(Request $request): Response
{
// On crée une instance du formulaire
$form = $this->createForm(ContactType::class);
// On lie le formulaire avec les données de la requête
$form->handleRequest($request);
// La suite du traitement vient ici...
return $this->render('contact/index.html.twig', [
'controller_name' => 'ContactController',
// On envoie le formulaire à la vue
'form' => $form->createView(),
]);
}
}
Étape 4 : Traiter les Données Soumises (`isSubmitted` et `isValid`)
Une fois que l’utilisateur clique sur « Envoyer », les données du formulaire sont envoyées au serveur. Notre contrôleur doit intercepter ces données, vérifier si elles sont valides, et ensuite effectuer une action (comme envoyer un email).
Pour cela, on utilise une simple condition `if`. On vérifie deux choses avec les méthodes du formulaire :
isSubmitted(): Vérifie si le formulaire a bien été envoyé (si l’utilisateur a cliqué sur le bouton).isValid(): Vérifie si les données respectent les règles de validation (on verra ça plus tard).
Si ces deux conditions sont vraies, on peut récupérer les données avec $form->getData(). Cela nous retourne un tableau associatif avec les valeurs des champs. Ensuite, on peut effectuer notre logique métier. Une fois le traitement terminé, c’est une bonne pratique d’afficher un message flash de succès avec addFlash et de faire une redirection. La redirection évite que l’utilisateur ne renvoie le formulaire par erreur en rafraîchissant la page.
Complétons notre contrôleur :
<?php
// src/Controller/ContactController.php
namespace App\Controller;
// ... (les use)
class ContactController extends AbstractController
{
#[Route('/contact', name: 'app_contact')]
public function index(Request $request): Response
{
$form = $this->createForm(ContactType::class);
$form->handleRequest($request);
// On vérifie si le formulaire est soumis et valide
if ($form->isSubmitted() && $form->isValid()) {
// On récupère les données
$data = $form->getData();
$nom = $data['nom'];
$email = $data['email'];
// Ici, vous mettriez votre logique : envoi d'email, sauvegarde en BDD...
// Par exemple :
// $mailer->sendEmail($email, $nom, $data['message']);
// On ajoute un message flash pour informer l'utilisateur
$this->addFlash('success', 'Votre message a bien été envoyé !');
// On redirige pour éviter la resoumission du formulaire
return $this->redirectToRoute('app_contact');
}
return $this->render('contact/index.html.twig', [
'form' => $form->createView(),
]);
}
}
Étape 5 : Afficher le Formulaire dans un Template Twig
La dernière étape consiste à afficher le formulaire en HTML. Dans le contrôleur, on passe le formulaire à la vue avec la ligne `return $this->render(…)`. Notez bien l’utilisation de `createView()`. On ne passe pas l’objet `$form` directement, mais sa « vue ». C’est obligatoire pour que Twig puisse l’afficher.
Dans le template `templates/contact/index.html.twig`, Symfony nous donne des fonctions Twig très simples pour générer le code HTML du formulaire. Il y en a trois principales :
form_start(form): Génère la balise `form_widget(form): Affiche tous les champs du formulaire d’un coup. Pratique pour aller vite.form_end(form): Génère tous les champs cachés (comme le jeton CSRF pour la sécurité) et la balise « de fermeture.
Voici un template de base pour afficher notre formulaire :
{# templates/contact/index.html.twig #}
{% extends 'base.html.twig' %}
{% block title %}Contactez-nous{% endblock %}
{% block body %}
{# Affichage des messages flash #}
{% for message in app.flashes('success') %}
<div class="alert alert-success">
{{ message }}
</div>
{% endfor %}
{{ form_start(form) }}
{{ form_widget(form) }}
{{ form_end(form) }}
{% endblock %}
Pour plus de contrôle, au lieu d’utiliser `form_widget(form)`, vous pouvez afficher chaque champ individuellement avec la fonction `form_row()`. Ça permet de personnaliser l’ordre et le style HTML autour de chaque champ. Par exemple : `{{ form_row(form.nom) }}`.
Pour aller plus loin : Validation et Entités Doctrine
Le formulaire est fonctionnel, mais on peut encore l’améliorer. Deux cas d’usage sont très courants : la validation des données et la liaison avec une entité de base de données.
Ajouter la validation des données
Pour l’instant, l’utilisateur peut envoyer un formulaire vide. Ce n’est pas idéal. On veut s’assurer que le nom n’est pas vide et que le message a une longueur minimale. Pour ça, on utilise le composant Validator de Symfony. On ajoute des `constraints` (contraintes) directement dans le `FormType`.
Par exemple, pour rendre le champ `nom` obligatoire et le champ `message` d’au moins 10 caractères, on modifie `ContactType.php` :
<?php
// src/Form/ContactType.php
namespace App\Form;
// ... (autres use)
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank;
class ContactType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('nom', TextType::class, [
'label' => 'Votre nom',
'constraints' => [
new NotBlank(['message' => 'Le nom ne peut pas être vide.']),
],
])
->add('message', TextareaType::class, [
'label' => 'Votre message',
'constraints' => [
new NotBlank(['message' => 'Le message ne peut pas être vide.']),
new Length(['min' => 10, 'minMessage' => 'Le message doit faire au moins {{ limit }} caractères.']),
],
])
// ... autres champs
;
}
// ...
}
Maintenant, si l’utilisateur soumet des données invalides, `$form->isValid()` retournera `false` et Twig affichera automatiquement les messages d’erreur à côté des champs concernés. Vous pouvez trouver toutes les contraintes de validation officielles dans la documentation.
Lier le formulaire à une entité Doctrine
Dans beaucoup de cas, un formulaire sert à créer ou modifier un objet en base de données (un produit, un article, un utilisateur…). Dans ce cas, il est bien plus simple de lier directement le formulaire à une entité Doctrine.
Pour ce faire, on utilise l’option `data_class` dans la méthode `configureOptions` du `FormType`. On indique simplement à quelle classe d’entité le formulaire correspond. Par exemple, pour un formulaire `ArticleType` lié à une entité `Article` :
<?php
// src/Form/ArticleType.php
namespace App\Form;
use App\Entity\Article;
// ...
class ArticleType extends AbstractType
{
// ... méthode buildForm
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Article::class,
]);
}
}
L’avantage est énorme. Quand vous appelez `handleRequest`, Symfony va automatiquement remplir (hydrater) un objet `Article` avec les données du formulaire. Et si vous passez un objet `Article` existant à `createForm`, le formulaire sera pré-rempli. C’est une des plus grandes forces de l’ intégration avec Doctrine.
Questions Fréquentes (FAQ)
Comment personnaliser le rendu d’un seul champ dans Twig ?
Au lieu d’utiliser `form_row(form.fieldName)`, vous pouvez décomposer le rendu en trois parties pour un contrôle total sur le HTML : `form_label()`, `form_widget()` (juste le champ input), et `form_errors()`.
<div class="custom-form-group">
{{ form_label(form.nom, 'Votre nom personnalisé') }}
{{ form_widget(form.nom, {'attr': {'class': 'custom-input'}}) }}
{{ form_errors(form.nom) }}
</div>
Comment créer un champ de type `select` (liste déroulante) ?
Il faut utiliser le type de champ `ChoiceType`. Vous devez lui passer un tableau d’options dans l’option `choices`.
Pourquoi mon formulaire n’est jamais « valid » (`isValid()` retourne false) ?
Les raisons les plus courantes sont :
- Les contraintes de validation ne sont pas respectées : Vérifiez les messages d’erreur affichés par Twig.
- Le jeton CSRF est manquant ou invalide : Assurez-vous d’avoir bien `form_end(form)` dans votre template Twig, car c’est lui qui génère le champ caché CSRF.
Comment gérer un upload de fichier ?
Pour les fichiers, il faut utiliser le type de champ `FileType`. La gestion est un peu plus complexe car il faut s’assurer que la balise `
