Récoltez des données avec un formulaire
Vous avez désormais une base de données prête à être utilisée. Il va falloir à présent la remplir. Ça tombe bien, Amélie vient d’appeler, elle aimerait avoir rapidement une page avec un formulaire pour commencer à ajouter des livres dans la base de données. La médiathèque a un catalogue assez conséquent, et elle aimerait prendre de l’avance. Vous vous tournez vers Sébastien pour savoir quoi faire. Il vous répond avec un plaisir évident.
Ça tombe bien. On va voir le composant Form de Symfony, il sert exactement à ça !
Le composant Form, bien qu’un peu particulier dans son approche, va vous permettre de créer, gérer et afficher des formulaires très facilement.
Commençons par dégrossir un peu son fonctionnement et résumer ce que nous allons faire :
Nos champs de formulaire sont représentés par des objets qui implémentent
Symfony\Component\Form\FormTypeInterface
(appelés aussiFormType
). Ces objets fonctionnent comme des poupées russes : uneFormType
peut contenir d’autresFormType
, et ainsi de suite. En conséquence, ces objets représenteront parfois un champ spécifique de notre formulaire, ou bien une collection de champs.Nous créerons des classes qui implémentent
FormTypeInterface
pour représenter les champs que nous désirons dans notre formulaire.Dans nos controllers, nous pouvons créer un formulaire grâce au raccourci
createForm
, en lui passant le nom de notre classe deFormType
et un objet à remplir avec les données envoyées par l’utilisateur.Les données envoyées par l'utilisateur seront insérées dans notre objet
FormInterface
(et dans l'objet sur lequel il se base le cas échéant) en passant l'objet Request à sa méthodehandleRequest
.Nous pouvons afficher notre formulaire grâce à des fonctions Twig spécifiques. En interne, Symfony appellera la méthode
createView
de laFormInterface
pour générer la représentation visuelle de notre formulaire.
Ça fait beaucoup là… non ?
Allez, un petit schéma aidera peut-être un peu.
C’est toujours étrange au premier abord, nous sommes d’accord. Rien de tel que la pratique pour mieux comprendre, cependant.
Créez un FormType
MakerBundle vous permet de créer rapidement des classes de formulaires appelées FormTypes avec sa commande
make:form
.Vous pouvez créer ces classes en les basant sur une classe.
La commande préremplit votre FormType avec des champs se basant sur les propriétés de la classe rattachée.
Par convention, les noms de classe de
FormType
se terminent toujours parType
. D’ailleurs, si vous ne respectez pas cette convention, Maker ajoutera le suffixe à votre classe tout seul.
Ouvrons maintenant le fichier qui a été créé. Pour l’instant, le composant Form détermine tout seul quel type de champ correspond à quelle propriété en se basant sur le mapping Doctrine. Mais il y a des limites à ses super pouvoirs, et nous allons devoir l’aider un peu.
Penchons-nous un peu plus sur la méthode $builder->add()
de la FormBuilderInterface
. Elle prend un paramètre obligatoire, et deux optionnels :
Le nom du champ à créer, seul paramètre obligatoire.
Le nom de la classe (par exemple la constante
::class
) qui représente ce champ (elle doit étendreFormTypeInterface
).Un array d’options supplémentaires qui dépendent du type de champ sélectionné en second paramètre. Notons que tous les champs ont des options en commun, comme
required
, qui définit si l’attribut HTMLrequired=required
sera ajouté dans notre champ (la valeur de cette option esttrue
par défaut, rendant tous vos champs requis).
Vous en avez un bon exemple avec le champ books
:
<?php
//…
->add('books', EntityType::class, [
'class' => Book::class,
'choice_label' => 'id',
'multiple' => true,
])
Et on met quoi concrètement comme type de champ ou comme options ? Je ne les connais pas !
La liste exhaustive des types disponibles avec leurs options se trouve sur la documentation officielle de Symfony. Lisez bien attentivement les descriptions avant de choisir l’un ou l’autre, certains ont des applications bien précises.
Retravaillons un peu notre formulaire. En nous basant sur la documentation officielle, nous pouvons déterminer les points suivants :
le nom de l’auteur sera un
TextType
, et nous n’avons pas grand-chose de plus à préciser ;la date de naissance et la date de décès seront tous les deux des
DateType
: pas besoin de prendre desDateTimeType
, l’heure de naissance ou de décès n’est pas importante, et leBirthdayType
est limité par défaut à un nombre d’années qui commence 120 ans avant la date du jour ;le
DateType
est par défaut représenté par trois listes déroulantes. Par commodité, on préférera un champ texte simple ;le
DateType
est par défaut converti en objetDateTime
de PHP. Or, nous utilisons duDateTimeImmutable
, il faudra donc aussi le préciser ;le champ
dateOfDeath
peut être vide, tous les auteurs ne sont pas morts ;le champ
nationality
peut être affiché sous forme de champ texte et ne doit pas être requis ;le champ
books
n’est pas requis.
Normalement, votre classe devrait ressembler à peu près à ça :
<?php
// …
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
// …
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('name', TextType::class)
->add('dateOfBirth', DateType::class, [
'input' => 'datetime_immutable',
'widget' => 'single_text',
])
->add('dateOfDeath', DateType::class, [
'input' => 'datetime_immutable',
'widget' => 'single_text',
'required' => false,
])
->add('nationality', TextType::class, [
'required' => false,
])
->add('books', EntityType::class, [
'class' => Book::class,
'choice_label' => 'id',
'multiple' => true,
‘required’ => false,
])
;
}
Et voilà ! Votre premier FormType
est prêt à être utilisé.
D’accord, mais je ne vois toujours pas de formulaire là…
C’est exactement ce que nous allons faire maintenant : afficher notre formulaire.
Affichez notre formulaire
Pour afficher notre formulaire d’ajout de nouvel auteur, il nous faut une page. Bien entendu, il est hors de question pour Amélie que cette page soit accessible au public. Pour l’instant nous ne savons pas sécuriser les pages, mais ça viendra. En attendant, allons créer des routes qui commencent par /admin
. Revenons sur notre terminal et lançons une nouvelle fois la commande make:controller
. Cette fois cependant, comme nom de controller, entrez Admin\AuthorController
.
Eh oui ! Maker a créé votre controller dans un sous-dossier. Ouvrez votre nouveau fichier. La route par défaut est /admin/author
. C’est parfait. Nous allons rajouter des routes dans ce fichier, à commencer par celle de notre formulaire : /admin/author/new
.
Utilisez les routes directement sur vos classes
Sébastien, qui passe derrière vous à ce moment, s’exclame :
Tu vas pas répéter
/admin/author
à chaque fois ! Utilise plutôt un préfixe.
Et devant votre regard perplexe, Il vous montre en écrivant un attribut Route
directement au-dessus du nom de la classe, et en supprimant le chemin de la route par défaut :
<?php
// …
#[Route('/admin/author')]
class AuthorController extends AbstractController
{
#[Route('', name: 'app_admin_author_index')]
public function index(): Response
{
// …
Voilà, à partir de maintenant, toutes les routes que tu crées dans ce fichier commenceront automatiquement par
/admin/author
, plus besoin de l’écrire dans chaque route !
Parfait. Il nous reste à créer une nouvelle route, le controller qui va avec, et un template pour afficher notre formulaire. Pour le template, vous pouvez dupliquer le template templates/admin/author/index.html.twig
par un copier-coller, et nommer le nouveau templates/admin/author/new.html.twig
. Nous le modifierons après.
Pour notre FormType
, la bonne pratique est de créer d’abord l’objet sur lequel il se base, ici une instance de Author
. Ensuite, vous pouvez créer votre formulaire en appelant le raccourci createForm
des controllers, en lui passant le nom de votre FormType
et votre instance de Author
. Il ne reste plus qu’à passer le formulaire au template :
<?php
// ...
#[Route('/new', name: 'app_admin_author_new', methods: ['GET'])]
public function new(): Response
{
$author = new Author();
$form = $this->createForm(AuthorType::class, $author);
return $this->render('admin/author/new.html.twig', [
'form' => $form,
]);
}
Utilisez les fonctions Twig d’affichage des formulaires
Passons maintenant au template. Pour afficher un formulaire dans un template, on passera notre variable form
à plusieurs fonctions :
form_start(form)
génère l’affichage de la balise d’ouverture de notre formulaire ;form_widget(form)
génère l’affichage des champs de notre formulaire, avec leurs labels et erreurs éventuelles ;alternativement, appelez
form_row(form.[nom_d_un_champ])
pour afficher un seul champ avec son label et son erreur éventuelle ;form_end(form)
génère l’affichage de la balise de fin de notre formulaire, ainsi que l’affichage de tous les champs que vous auriez oublié d’afficher.
Changeons donc le contenu de notre template. Nous allons afficher notre formulaire, sans oublier de changer le titre de la page :
{% extends 'base.html.twig' %}
{% block title %}Ajout d'auteur{% endblock %}
{% block body %}
<style>
.example-wrapper { margin: 1em auto; max-width: 800px; width: 95%; font: 18px/1.5 sans-serif; }
.example-wrapper code { background: #F5F5F5; padding: 2px 6px; }
</style>
<div class="example-wrapper">
<h1>Ajout d'auteur</h1>
{{ form_start(form) }}
{{ form_widget(form) }}
{{ form_end(form) }}
</div>
{% endblock %}
Si vous vous rendez maintenant sur la page 127.0.0.1:8000/admin/author/new
dans votre navigateur… le résultat n’est pas terrible.
Commençons par utiliser ce qu’on appelle un thème de formulaire. Puisque nous avons installé Bootstrap, profitons-en. Symfony embarque plusieurs thèmes Bootstrap pour le composant Form, nous allons en choisir un. Pour l’activer, ouvrez le fichier config/packages/twig.yaml
et ajoutez la ligne suivante :
twig:
// …
form_themes:
- 'bootstrap_5_layout.html.twig'
// …
Le résultat :
Nettement mieux ! Mais il manque encore quelque chose : un bouton de soumission. Ce bouton pourrait être ajouté directement dans le FormType
en ajoutant un champ de type SubmitType
. Mais les bonnes pratiques Symfony recommandent de mettre ce bouton en tant que bon vieux bouton HTML directement dans vos templates afin de favoriser la réutilisation de vos FormType
. Revenez dans templates/admin/author/new.html.twig
:
// …
{{ form_start(form) }}
{{ form_widget(form) }}
<button type="submit" class="btn btn-primary">Envoyer</button>
{{ form_end(form) }}
// …
Dans le même ordre d’idées, il serait bien de changer les labels pour les mettre en français. Vous pouvez ajouter une option label
sur tous les champs dans votre FormType
, par exemple dans notre AuthorType
:
<?php
// …
$builder
->add('name', TextType::class, [
'label' => 'Nom',
])
// …
Vous pouvez aussi, à la place, afficher vos champs ligne par ligne dans votre template et changer le label en passant un second argument à la méthode form_row
, par exemple dans new.html.twig
:
// …
{{ form_start(form) }}
{{ form_row(form.name, {label: 'Nom'}) }}
{{ form_row(form.dateOfBirth, {label: 'Date de Naissance'}) }}
// …
Notre formulaire est affiché, les labels sont bons, reste à récupérer les données envoyées lorsque l’utilisateur clique sur le bouton Envoyer.
Traitez les données renvoyées par le formulaire
Pour faire ça, une seule et unique méthode.
Retournez dans votre controller
Admin\AuthorController
, dans votre méthodenew
.Vous allez commencer par demander à Symfony de recevoir l’objet
Symfony\Component\HttpFoundation\Request
comme argument.Ensuite, vous pourrez demander à votre formulaire de traiter la requête.
Il ne vous reste qu’à vérifier que le formulaire a bien été soumis et qu’il est bien valide (que les champs requis sont présents, par exemple).
Et n’oubliez pas d’autoriser la méthode
POST
, si vous voulez recevoir les données du formulaire…
<?php
// …
#[Route('/new', name: 'app_admin_author_new', methods: ['GET', 'POST'])]
public function new(Request $request): Response
{
$author = new Author();
$form = $this->createForm(AuthorType::class, $author);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// faire quelque chose
}
// …
Faites un essai : remplissez votre formulaire, cliquez sur Envoyer.
Allez ensuite voir dans le Profiler ce qu’il s’est passé. Pour afficher la dernière requête reçue par le serveur, cliquez sur Latest tout en haut du menu à gauche de la page :
Si vous allez dans l’onglet Forms, vous pourrez voir tout de suite que votre objet Author
a été correctement rempli avec les données du formulaire.
Il ne nous reste maintenant plus qu'à trouver quoi faire de ces données que nous recevons !
À vous de jouer
Contexte
Vous avez un bon exemple de création de formulaire, mais votre application n'est pas complète sur ce point pour autant. Il vous reste encore des formulaires à créer pour certaines entités afin qu'Amélie et son équipe puissent remplir la base de données.
Consignes
Créez maintenant les formulaires pour les entités Editor et Book, ainsi que les pages d’administration (controllers et templates) qui vont avec. N'hésitez pas à vous inspirer du code déjà écrit ou à le réutiliser pour le formulaire de l'entité Author
. Attention aux types de vos champs et à leurs options.
Pour le design, reprenez les mêmes templates que pour l'entité Author
, en adaptant en fonction des champs de vos entités.
En résumé
Le composant Form sert à créer, gérer et afficher nos formulaires.
Nos formulaires sont représentés sous forme de classes étendant
AbstractType
.Ces objets peuvent être utilisés dans nos controllers pour récupérer les données envoyées par l’utilisateur dans la requête et les hydrater dans nos entités.
Twig dispose de fonctions pour faciliter l’affichage de formulaires.
Voyons maintenant comment sauvegarder en base les données que nous avons reçues grâce à Doctrine !