• 12 hours
  • Hard

Free online content available in this course.

course.header.alt.is_certifying

Got it!

Last updated on 8/26/24

Récoltez des données à sauvegarder

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 aussi  FormType  ). Ces objets fonctionnent comme des poupées russes : une  FormType  peut contenir d’autres  FormType  , 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 de  FormType  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éthode  handleRequest  .

  • Nous pouvons afficher notre formulaire grâce à des fonctions Twig spécifiques. En interne, Symfony appellera la méthode  createView  de la  FormInterface  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.

Schéma de récolte d'informations pour un formulaire
Schéma de récolte d'informations pour un formulaire

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 par  Type  . 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 :

  1. Le nom du champ à créer, seul paramètre obligatoire.

  2. Le nom de la classe (par exemple la constante  ::class  ) qui représente ce champ (elle doit étendre  FormTypeInterface  ).

  3. 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 HTML  required=required  sera ajouté dans notre champ (la valeur de cette option est  true  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 des  DateTimeType  , l’heure de naissance ou de décès n’est pas importante, et le  BirthdayType  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 objet  DateTime  de PHP. Or, nous utilisons du  DateTimeImmutable  , 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  .

Création du controller
Création du controller

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.

Affichage à améliorer
Affichage à améliorer

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 :

Affichage amélioré
Affichage amélioré

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.

  1. Retournez dans votre controller  Admin\AuthorController  , dans votre méthode  new  . 

  2. Vous allez commencer par demander à Symfony de recevoir l’objet  Symfony\Component\HttpFoundation\Request  comme argument.

  3. Ensuite, vous pourrez demander à votre formulaire de traiter la requête. 

  4. 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). 

  5. 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.

Formulaire rempli
Formulaire rempli

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 :

Affichage de la dernière requête reçue
Affichage de la dernière requête reçue

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.

Objet Author rempli avec les données issues du formulaire
Objet Author rempli avec les données issues 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 !

Example of certificate of achievement
Example of certificate of achievement