
Introduction aux formulaires Symfony pour ajouter une tâche
Découvrez comment créer et gérer des formulaires dans Symfony pour ajouter une nouvelle tâche. Ce guide aborde la création de FormType, l'intégration dans Twig, la gestion de la soumission et les redirections.
Pourquoi utiliser le composant Form de Symfony ?
Jusqu'à présent, notre gestionnaire de tâches permet de visualiser des données existantes. Cependant, toute application un tant soit peu interactive nécessite de pouvoir soumettre des données, que ce soit pour créer de nouvelles entrées, modifier des informations existantes, ou effectuer des recherches. La gestion des formulaires HTML, incluant leur affichage, la validation des données saisies par l'utilisateur, et le traitement de ces données côté serveur, peut rapidement devenir complexe et répétitive.
Le composant Form de Symfony est une solution élégante et puissante pour abstraire cette complexité. Il offre un cadre structuré pour :
- Construire des formulaires en PHP de manière orientée objet.
- Afficher facilement ces formulaires dans les templates Twig.
- Gérer la soumission des données (GET ou POST).
- Valider les données soumises par rapport à des contraintes définies.
- Mapper les données du formulaire vers des objets ou des tableaux PHP, et inversement.
- Protéger contre les attaques CSRF (Cross-Site Request Forgery) automatiquement.
En utilisant le composant Form, vous écrivez moins de code répétitif, améliorez la sécurité de votre application et rendez vos formulaires plus faciles à maintenir et à faire évoluer. Pour notre projet, nous allons l'utiliser pour permettre à l'utilisateur d'ajouter une nouvelle tâche à notre liste.
Créer un type de formulaire simple avec `make:form`
La première étape pour créer un formulaire dans Symfony est de définir sa structure. Cela se fait en créant une classe de type de formulaire (communément appelée "FormType"). Cette classe décrit les champs qui composent le formulaire, leurs types (texte, nombre, case à cocher, etc.), leurs options (libellé, requis ou non, etc.) et potentiellement des contraintes de validation.
Symfony fournit une commande `make:form` pour générer rapidement le squelette d'une classe de type de formulaire. Supposons que nous voulons créer un formulaire pour une tâche, qui aura un champ pour le titre et un champ pour la description. Nous pouvons nommer ce type de formulaire `TaskType`. Exécutez la commande suivante dans votre terminal :
php bin/console make:form TaskTypeSymfony vous demandera peut-être à quelle entité ce formulaire est lié. Pour l'instant, comme nous n'utilisons pas d'entités Doctrine, vous pouvez laisser ce champ vide ou simplement appuyer sur Entrée. Cette commande créera un fichier src/Form/TaskType.php.
Ouvrez ce fichier. Vous verrez une méthode buildForm(FormBuilderInterface $builder, array $options). C'est ici que nous allons définir les champs de notre formulaire. Pour une tâche, nous avons besoin d'un champ pour le titre et un pour la description. Nous pouvons également ajouter un champ pour marquer la tâche comme complétée (une case à cocher). Modifiez la méthode buildForm comme suit :
namespace App\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class TaskType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('title', TextType::class, [
'label' => 'Titre de la tâche',
'attr' => ['placeholder' => 'Que faut-il faire ?'],
'required' => true, // Champ requis
])
->add('description', TextareaType::class, [
'label' => 'Description (optionnel)',
'attr' => ['rows' => 5],
'required' => false, // Champ non requis
])
// Pour l'instant, nous n'ajoutons pas 'completed' ici car la logique
// de création suppose une tâche non complétée par défaut.
// Nous pourrions l'ajouter pour un formulaire d'édition.
/*
->add('completed', CheckboxType::class, [
'label' => 'Marquer comme terminée ?',
'required' => false,
])
*/
->add('save', SubmitType::class, [
'label' => 'Ajouter la tâche',
'attr' => ['class' => 'btn btn-primary mt-3'],
]);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
// Configurez ici les options par défaut pour votre formulaire.
// Par exemple, si vous liez ce formulaire à une entité Doctrine :
// 'data_class' => Task::class,
// Pour l'instant, nous n'avons pas de data_class car nous utilisons un tableau.
]);
}
}Chaque appel à $builder->add() ajoute un champ au formulaire. Le premier argument est le nom du champ (qui correspondra à une clé dans les données du formulaire). Le deuxième argument est le type de champ (par exemple, TextType::class pour un champ texte simple, TextareaType::class pour une zone de texte multiligne, SubmitType::class pour un bouton de soumission). Le troisième argument est un tableau d'options pour configurer le champ (libellé, attributs HTML, etc.).
Intégrer et afficher le formulaire dans un template Twig
Une fois notre TaskType défini, nous devons créer une action dans notre TaskController pour afficher ce formulaire et une route associée. Ajoutons une méthode newTask() (ou create()) :
// Dans src/Controller/TaskController.php
use App\Form\TaskType; // N'oubliez pas d'importer votre FormType
use Symfony\Component\HttpFoundation\Request; // Et l'objet Request
// ... (autres méthodes)
#[Route('/tache/nouvelle', name: 'app_task_create', methods: ['GET', 'POST'])]
public function newTask(Request $request): Response
{
// Pour l'instant, les données du formulaire ne seront pas persistées.
// Nous créons un tableau vide pour 'hydrater' le formulaire initialement.
// Dans un cas réel avec Doctrine, ce serait une nouvelle instance de votre entité Task.
$taskData = [];
$form = $this->createForm(TaskType::class, $taskData);
// La suite pour gérer la soumission sera ajoutée à l'étape suivante
return $this->render('task/new.html.twig', [
'taskForm' => $form->createView(),
]);
}La méthode $this->createForm(TaskType::class, $taskData) instancie notre formulaire. $taskData est un tableau (ou un objet si vous utilisez data_class) qui peut être utilisé pour pré-remplir le formulaire. Nous passons ensuite la vue du formulaire ($form->createView()) au template Twig.
Créez maintenant le template templates/task/new.html.twig :
{# templates/task/new.html.twig #}
{% extends 'base.html.twig' %}
{% block title %}Ajouter une nouvelle tâche{% endblock %}
{% block body %}
<div class="container mt-4">
<h1>Ajouter une nouvelle tâche</h1>
{# Affiche le formulaire. form_start, form_widget et form_end sont des fonctions Twig #}
{{ form_start(taskForm) }}
{# Affiche les erreurs globales du formulaire, s'il y en a #}
{{ form_errors(taskForm) }}
{# Affiche chaque champ du formulaire individuellement ou tout d'un coup #}
{# Option 1: Afficher tous les champs non encore rendus #}
{# {{ form_widget(taskForm) }} #}
{# Option 2: Afficher chaque champ manuellement pour plus de contrôle sur le HTML #}
<div class="mb-3">
{{ form_label(taskForm.title) }}
{{ form_widget(taskForm.title, {'attr': {'class': 'form-control'}}) }}
{{ form_errors(taskForm.title) }}
<small class="form-text text-muted">{{ form_help(taskForm.title) }}</small>
</div>
<div class="mb-3">
{{ form_label(taskForm.description) }}
{{ form_widget(taskForm.description, {'attr': {'class': 'form-control'}}) }}
{{ form_errors(taskForm.description) }}
<small class="form-text text-muted">{{ form_help(taskForm.description) }}</small>
</div>
{# Le bouton de soumission est déjà défini dans TaskType, mais on peut le surcharger #}
{# Si vous avez déjà un bouton 'save' dans votre TaskType, vous pouvez omettre la ligne suivante #}
{# ou utiliser {{ form_row(taskForm.save) }} #}
{# Sinon, si vous voulez le bouton ici : #}
{# <button type="submit" class="btn btn-primary">Ajouter la tâche</button> #}
{# Affiche les champs restants (y compris le bouton submit si défini dans le FormType et pas encore affiché) #}
{# et le token CSRF #}
{{ form_rest(taskForm) }}
{{ form_end(taskForm) }}
<p class="mt-3">
<a href="{{ path('app_task_list') }}" class="btn btn-secondary">Annuler et retourner à la liste</a>
</p>
</div>
{% endblock %}Twig fournit des fonctions très utiles pour afficher les formulaires :
form_start(form): Affiche la balised'ouverture, avec la méthode et l'action correctes.form_widget(form.fieldName): Affiche le widget (input, textarea, select) pour un champ spécifique.form_label(form.fieldName): Affiche le libellé du champ.form_errors(form.fieldName): Affiche les erreurs de validation pour ce champ.form_help(form.fieldName): Affiche un message d'aide pour le champ (si défini).form_row(form.fieldName): Raccourci qui affiche le libellé, le widget, les erreurs et l'aide pour un champ.form_rest(form): Affiche tous les champs du formulaire qui n'ont pas encore été affichés explicitement (utile pour les champs cachés comme le token CSRF).form_end(form): Affiche la balisede fermeture et tous les champs non encore rendus si ce n'est pas déjà fait parform_rest.
En visitant l'URL /tache/nouvelle, vous devriez maintenant voir votre formulaire s'afficher.
Gérer la soumission du formulaire dans le contrôleur
Afficher le formulaire n'est que la première moitié du travail. Nous devons maintenant gérer ce qui se passe lorsque l'utilisateur le soumet. Modifions notre méthode newTask() dans TaskController pour traiter la soumission :
// Dans src/Controller/TaskController.php
// ... (imports et début de la classe)
#[Route('/tache/nouvelle', name: 'app_task_create', methods: ['GET', 'POST'])]
public function newTask(Request $request): Response
{
$taskData = []; // Ou une nouvelle entité Task si vous utilisez Doctrine
$form = $this->createForm(TaskType::class, $taskData);
// Gère la requête : si le formulaire est soumis, les données de la requête
// sont mappées sur l'objet/tableau $taskData lié au formulaire.
$form->handleRequest($request);
// Vérifie si le formulaire a été soumis ET si les données sont valides
// (La validation sera abordée plus en détail plus tard, pour l'instant,
// elle se base sur les types de champs et l'option 'required')
if ($form->isSubmitted() && $form->isValid()) {
// Récupère les données soumises et validées
$submittedData = $form->getData();
// Logique pour "sauvegarder" la tâche.
// Pour l'instant, nous allons simuler cela en l'ajoutant à une session
// ou en l'affichant simplement. Dans un vrai projet, vous la persisteriez en base de données.
// Exemple : affichage des données (pour le débogage)
// dump($submittedData);
// return new Response('<html><body>Tâche soumise : <pre>' . print_r($submittedData, true) . '</pre></body></html>');
// Simulation d'ajout à notre tableau de tâches (propriété du contrôleur)
// ATTENTION : Ceci n'est PAS persistant entre les requêtes dans un vrai scénario
// car le contrôleur est instancié à chaque requête. Pour une persistance temporaire,
// il faudrait utiliser la session.
$newId = count($this->tasks) > 0 ? max(array_column($this->tasks, 'id')) + 1 : 1;
$newTask = [
'id' => $newId,
'title' => $submittedData['title'],
'description' => $submittedData['description'],
'completed' => false, // Par défaut, une nouvelle tâche n'est pas complétée
];
// $this->tasks[] = $newTask; // Ne fonctionne pas comme attendu pour la persistance
// Pour une démonstration rapide sans persistance réelle, utilisons un message flash
$this->addFlash(
'success',
sprintf('La tâche "%s" a été ajoutée (simulation) !', $submittedData['title'])
);
// Redirection vers la liste des tâches après une soumission réussie
// C'est une bonne pratique (Post/Redirect/Get pattern) pour éviter
// les doubles soumissions si l'utilisateur rafraîchit la page.
return $this->redirectToRoute('app_task_list');
}
return $this->render('task/new.html.twig', [
'taskForm' => $form->createView(),
]);
}Points importants de cette logique de traitement :
$form->handleRequest($request): Cette ligne est cruciale. Elle prend l'objetRequestde Symfony (qui contient toutes les informations de la requête HTTP, y compris les données POST) et tente de mapper ces données sur l'objet ou le tableau ($taskData) qui a été lié au formulaire lors de sa création.$form->isSubmitted(): Vérifie si le formulaire a effectivement été soumis (par exemple, si le bouton de soumission a été cliqué).$form->isValid(): Vérifie si les données soumises respectent les règles de validation. Pour l'instant, cela inclut principalement si les champs requis sont remplis et si les données correspondent aux types de champs. Nous verrons la validation plus avancée plus tard.$form->getData(): Si le formulaire est soumis et valide, cette méthode retourne les données nettoyées et mappées (dans notre cas, un tableau associatif avec les cléstitleetdescription).$this->addFlash('success', 'Message'): Permet d'enregistrer un message "flash" qui sera affiché sur la prochaine page après une redirection. C'est utile pour les messages de succès ou d'erreur.$this->redirectToRoute('app_task_list'): Redirige l'utilisateur vers une autre page (ici, la liste des tâches) après une soumission réussie. C'est le motif Post/Redirect/Get (PRG), une bonne pratique web.
Pour que les messages flash s'affichent, vous devez ajouter un peu de code dans votre template de base (templates/base.html.twig), généralement dans le bloc body :
{# Extrait de templates/base.html.twig #}
&t;body>
{% for label, messages in app.flashes %}
{% for message in messages %}
&t;div class="alert alert-{{ label }} m-3">
{{ message }}
&t;/div>
{% endfor %}
{% endfor %}
{% block body %}{% endblock %}
{# ... #}
&t;/body>{# Extrait de templates/base.html.twig #}
{% for label, messages in app.flashes %}
{% for message in messages %}
{{ message }}
{% endfor %}
{% endfor %}
{% block body %}{% endblock %}
{# ... #}
Redirection après soumission et affichage d'un message flash
Comme mentionné précédemment, la redirection après une soumission de formulaire réussie est une pratique essentielle. Elle empêche plusieurs problèmes, notamment la resoumission accidentelle des mêmes données si l'utilisateur actualise la page de confirmation (après un POST) ou utilise le bouton "Précédent" de son navigateur et resoumet le formulaire.
La méthode $this->redirectToRoute('nom_de_la_route', [paramètres_de_route_si_besoin]) est le moyen standard dans Symfony pour effectuer une redirection vers une route nommée. Dans notre cas, après avoir (simulé) l'ajout d'une tâche, nous redirigeons l'utilisateur vers app_task_list pour qu'il puisse voir la liste mise à jour (même si notre mise à jour n'est pas persistante pour le moment).
Les messages flash, configurés avec $this->addFlash('type_de_message', 'Votre message'), sont conçus pour être affichés une seule fois. Ils sont stockés dans la session de l'utilisateur et sont supprimés dès qu'ils sont lus et affichés par le template. C'est un excellent moyen de fournir un retour d'information concis à l'utilisateur sur le résultat de son action (par exemple, "Tâche ajoutée avec succès", "Erreur lors de la sauvegarde", etc.). Le premier argument de addFlash ('success', 'error', 'info', 'warning') peut être utilisé dans le template Twig pour styliser différemment les messages (par exemple, en utilisant différentes classes CSS pour les alertes Bootstrap).
En combinant la redirection et les messages flash, vous offrez une expérience utilisateur fluide et informative. L'utilisateur soumet le formulaire, est redirigé vers une page appropriée (souvent une page de liste ou de visualisation), et y voit un message confirmant le succès ou l'échec de son opération.
Avec ces éléments en place, vous avez maintenant les bases pour créer des formulaires, les afficher et gérer leur soumission dans Symfony. Bien que nous n'ayons pas encore abordé la persistance réelle des données ni la validation avancée, vous comprenez le flux de travail principal du composant Form.