TP n°7 - Implémentation d’un CRUD sur ToDo
Générateurs de formulaires CRUD avec Symfony

Table des matières

1. Introduction

Cette séance a pour but de comprendre la mise en œuvre des mécanismes qui permettent à une application Web de gérer la soumission de données de la part des utilisateurs, et la persistence de ces données, pour que son comportement dépende des interactions précédentes avec le même utilisateur, ou d’autres utilisateurs accédant aux mêmes données.

Jusqu’ici, nous avons travaillé sur une application dont le comportement en mode Web était basé sur la lecture seule des données, sans possibilité de mettre en œuvre une modification de ces données.

Les seules sources de modification des données étaient, soit une utilisation en ligne de commande, depuis le répertoire dans lequel l’application est installée, soit une modification du contenu de la base de données en tant qu’administrateur ayant accès au back-office Web fonctionnant grâce à EasyAdmin.

Pour mettre en œuvre la soumission des données via les pages Web de l’application, on va notamment s’appuyer sur les formulaires de saisie HTML, et la soumission des données de ces formulaires via les requêtes POST HTTP.

2. Étape 1 : Ajout de fonctions de soumission de données à ToDo

L’objectif de cette séquence est d’enrichir l’application « fil-rouge » de gestion de tâches ToDo pour que l’interface Web de l’application permette d’interagir totalement avec le modèle de données existant, y compris en modification.

Nous allons utiliser des assistants de génération de code de Symfony qui permettent de générer rapidement des contrôleurs complets implémentant des fonctionnalités CRUD.

2.1. Principe des générateurs de code CRUD Symfony

Nous avons déjà vu dans une séance précédente les générateurs :

  • d’entité (make:entity)
  • de contrôleur « basique », avec son template Twig par défaut (make:controller)
  • de CRUD EasyAdmin pour une entité, dans le backoffice (make:admin:crud)

Cette fois, nous allons utiliser l’assistant make:crud qui génère, pour une entité du modèle de l’application, un contrôleur, un jeu de formulaires, des templates et méthodes associées à chacune des opérations CRUD suivantes :

  • création d’une nouvelle entité (Create/new)
  • consultation (Read), déclinée en deux sous-ensembles :
    • d’une collection d’entités (index)
    • d’une entité (show)
  • modification d’une entité (Update/edit)
  • suppression d’une entité (Delete)

Nous allons travailler sur une nouvelle entité du modèle qu’on va ajouter à l’application ToDo : l’entité Paste. Elle va permettre de gérer des notes à copier/coller (« copy/paste »).

Nous avons choisi de travailler sur une nouvelle entité pour permettre la génération de fichiers source PHP sans risquer d’entrer en conflit avec le code existant déjà pour la consultation de l’entité Todo (src/Controller/TodoController.php).

2.2. TODO Étape 1-a : Ajout d’une nouvelle entité Paste

L’objectif de cette étape est de générer le code d’une nouvelle entité du Modèle des données Doctrine, qui gère des sauvegardes de « bouts de texte ».

Nous appelons cette entité Paste (collage), en référence au fait de garder une trace de copiés-collés dans une application Web (mémos, astuces, bouts de code, …).

Une paste sera identifiée par :

  • un contenu textuel (content)
  • une date de sauvegarde (created)
  • un type de contenu (content_type), optionnel, permettant éventuellement d’en typer le format avec un type MIME.

Attention : on utilise le type text et non string car les bouts de texte qu’on souhaite sauvegarder sont potentiellement longs.

Procédez aux étapes suivantes :

  1. Utilisez l’assistant générateur de code pour ajouter au modèle de données une nouvelle entité Doctrine :

    symfony console make:entity
    
    Class name of the entity to create or update (e.g. FierceElephant):
    > Paste
    
    created: src/Entity/Paste.php
    created: src/Repository/PasteRepository.php
    
    Entity generated! Now let's add some fields!
    You can always add more fields later manually or by re-running this command.
    
    New property name (press <return> to stop adding fields):
    > content
    
    Field type (enter ? to see all types) [string]:
    > text
    
    Can this field be null in the database (nullable) (yes/no) [no]:
    > 
    
    updated: src/Entity/Paste.php
    
    Add another property? Enter the property name (or press <return> to stop adding fields):
    > created
    
    Field type (enter ? to see all types) [string]:
    > datetime
    
    Can this field be null in the database (nullable) (yes/no) [no]:
    > 
    
    updated: src/Entity/Paste.php
    
    Add another property? Enter the property name (or press <return> to stop adding fields):
    > content_type
    
    Field type (enter ? to see all types) [string]:
    > 
    
    Field length [255]:
    > 
    
    Can this field be null in the database (nullable) (yes/no) [no]:
    > yes
    
    updated: src/Entity/Paste.php
    
    Add another property? Enter the property name (or press <return> to stop adding fields):
    > 
    
    
    
     Success! 
    
    
    Next: When you're ready, create a migration with symfony console make:migration
    
    
  2. Rafraîchissez le projet dans votre IDE, et vérifiez ensuite le code généré dans src/Entity/.

2.3. TODO Étape 1-b : Mise à jour du schéma de la base de données

L’objectif de cette étape est de découvrir l’outil de migrations de bases de données de Symfony

Les migrations sont des scripts qui permettent de gérer les changement du modèle de données à appliquer au SGBD. Nous ne les avons pas étudiés jusqu’ici, et ils concernent principalement la gestion des applications en production.

Jusqu’à présent, dans les séances de TP, à chaque évolution du modèle de données, on utilisait la séquence suivante pour aligner le contenu de la base de données avec le nouveau modèle de données décrit dans les classes Doctrine :

  1. suppression de la base de données : symfony console doctrine:database:drop --force
  2. recréation de la base de données : symfony console doctrine:database:create
  3. recréation du schéma de la base de données : symfony console doctrine:schema:create
  4. rechargement des données de tests : symfony console doctrine:fixtures:load -n

Cette séquence est appropriée en développement : on recrée la base de donnée en repartant de zéro, et on teste l’ajout de données via les Fixtures. Mais il n’est pas question d’en faire autant quand on a une base de données en production, si l’on souhaite redéployer une nouvelle version de l’application.

Dans ce cas, on devra disposer d’une procédure de migration de la base de données, qui fera la mise à jour dans la base de données de production, sans rien supprimer (espérons).

C’est justement ce que permet de faire le module Migrations de Symfony, dont il est question dans le message de fin affiché par make:entity : « Next: When you’re ready, create a migration with symfony console make:migration ».

Essayons maintenant :

  1. Générez un script de migration correspondant à la dernière évolution du schéma de la base de données, avec la commande suivante :

    symfony console make:migration
    
    Success! 
    
    Next: Review the new migration "migrations/Version20221012085829.php"
    Then: Run the migration with php bin/console doctrine:migrations:migrate
    See https://symfony.com/doc/current/bundles/DoctrineMigrationsBundle/index.html
    
    
  2. Observez dans votre IDE le script PHP qui vient d’être généré (migrations/Version20221012085829.php, ici).

    Vous remarquerez qu’il revient ici à appliquer un CREATE TABLE pour la nouvelle entité ajoutée.

  3. On peut donc appliquer cette migration à notre base de données. Il suffit de suivre les indications données dans ce message : « Run the migration with symfony console doctrine:migrations:migrate ».

    Donc lancez la commande suivante, et validez l’application de la migration :

    symfony console doctrine:migrations:migrate
    
    WARNING! You are about to execute a migration in database "main" that could result in schema changes and data loss. Are you sure you wish to continue? (yes/no) [yes]:
    
  4. Vous pouvez maintenant lancer l’application et re-tester. Elle devrait toujours fonctionner comme précédemment pour la consultation des tâches de la base de données.

Vous savez désormais comment appliquer des migrations à une base de données en production.

Mais tant que vous êtes en phase de développement de votre projet, le plus simple est peut-être de continuer à faire des database:create suivis de schema:create.

2.4. TODO Étape 1-c : Ajout d’un controleur CRUD pour les Pastes

L’objectif de cette étape est d’ajouter un contrôleur permettant de disposer de pages Web CRUD dans l’application, pour ajouter (Create), consulter (Retrieve) modifier (Update), ou supprimer (Delete), des instances de notre nouvelle entité Paste.

Procédez aux étapes suivantes :

  1. Ajoutez un nouveau contrôleur CRUD PasteController, pour la classe Paste :

    symfony console make:crud
    
    The class name of the entity to create CRUD (e.g. FierceElephant):
    > Paste
    
    Choose a name for your controller class (e.g. PasteController) [PasteController]:
    > PasteController
    
    Do you want to generate tests for the controller?. [Experimental] (yes/no) [no]:
    > 
    
    created: src/Controller/PasteController.php
    created: src/Form/PasteType.php
    created: templates/paste/_delete_form.html.twig
    created: templates/paste/_form.html.twig
    created: templates/paste/edit.html.twig
    created: templates/paste/index.html.twig
    created: templates/paste/new.html.twig
    created: templates/paste/show.html.twig
    
    
     Success! 
    
    
    Next: Check your new CRUD by going to /paste/
    
  2. Rafraîchissez le contenu du projet dans votre IDE, et consultez les fichiers générés.

    Vous pouvez voir que l’assistant a non-seulement généré le code PHP de la classe du contrôleur, dans src/Controller, mais aussi tous les éléments associés, comme des gabarits Twig nécessaires.

2.5. TODO Étape 1-d : Tests de l’application modifiée

Testez maintenant l’application qui permet maintenant une soumission de données :

  1. Consultez la liste des nouvelles routes, avec symfony console debug:router
  2. Lancez l’application comme d’habitude
  3. Testez l’application et vérifiez que vous pouvez maintenant saisir des données via les fonctions accessibles par les routes CRUD, derrière le préfixe http://localhost:8000/paste/

    Les boutons « Save » de validation des formulaires sont assez peu visibles pour l’instant, mais bien actifs

    Il se peut que vous constatiez des problèmes de fonctionnement du contrôleur CRUD généré ci-dessus, avec une route inconnue pour le chemin /paste/. Il est alors nécessaire de corriger cela :

    Il faut probablement à ce stade nettoyer le cache de Symfony :

    symfony console cache:clear
    

    Si jamais ça ne suffisait pas (un bug Symfony semble empêcher parfois de faire fonctionner les nouvelles routes, malheureusement…), vous pouvez réinitialiser le projet Symfony un peu plus en profondeur :

    rm -fr var composer.lock symfony.lock vendor
    symfony composer install
    
  4. Vérifiez que le formulaire d’ajout de nouvelle paste fonctionne enfin, en vérifiant l’impact sur la base de données : les pastes ajoutées sont bien retrouvées dans la liste, et ainsi de suite.

2.6. TODO Étape 1-e : Compréhension des requêtes mises en jeu dans un formulaire de création

  1. Vérifiez la séquence d’interactions HTTP, dans le fonctionnement des formulaires de création sur /paste/new :

    1. chargement du formulaire de création (1ère requête : GET)
    2. soumission de données à la validation du formulaire (2ème requête : POST)
    3. une fois la soumission traitée, redirection (automatique opérée par le navigateur) vers la liste des pastes (nouvelle requête GET)

    Vous pouvez suivre ces requêtes via l’outil réseau du développeur Web, dans le navigateur.

    Observez les en-têtes transmis dans la réponse à la requête POST : en complément du code de réponse 303, le serveur envoie la cible de la redirection, dans l’en-tête Location.

  2. Vérifiez aussi ce qui apparaît dans les logs du serveur HTTP (dans le terminal), où on voit passer ces 3 requêtes et les codes de réponse renvoyés (303 pour la redirection) :

    [200]: GET /paste/new
    ...
    [303]: POST /paste/new
    ...
    [200]: GET /paste/
    
  3. Observez également les caractéristiques du traitement de la requête POST en remontant dans l’historique des requêtes, via la barre d’outils Symfony (le profiler) :

    Une fois une nouvelle paste ajoutée, on est redirigé vers la page de liste des pastes. Si vous cliquez alors sur le code de réponse « 200 » dans la barre d’outils Symfony, vous accédez au Profiler qui indique :

    303 Redirect from POST @paste_new (544890)
    

    Cela signifie qu’on a affiché cette page de liste des pastes (envoyée avec succès : 200), car le navigateur nous y a redirigé. La redirection a été provoquée car il avait reçu, juste avant, un code de réponse 303 en réponse à sa requête POST (qui avait été faite sur la route paste_new). Elle avait été traîtée par le serveur Symfony qui lui a associé, en interne, le jeton de requête (Token) « 544890 », dans cet exemple.

    Remarquez que ce numéro de jeton de requête « 544890 » est en fait un lien qui permet d’aller inspecter tout ce qui s’est passé lors du traîtement de cette requête POST par le serveur Symfony.

  4. Cliquez sur ce lien de jeton de requête POST (ici « 544890 »). Vous voyez apparaître toutes les infos traîtées par Symfony concernant la requête POST de soumission des données, de façon similaire à ce que vous connaissez déjà pour les requêtes GET :
    • nom de la méthode du contrôleur invoquée : PasteController::new
    • données du POST :
      • content
      • create
    • etc.
  5. Cliquez alors sur l’outil « Doctrine »

    Vous voyez alors, à la fin, la requête SQL qui a été générée :

    1. START TRANSACTION
    2. INSERT INTO paste (content, created, content_type) VALUES (?, ?, ?) ...
    3. COMMIT
  6. Cliquez sur l’outil « Forms » : vous voyez des détails sur les données qui ont été soumises depuis le formulaire HTML via la méthode POST, et la façon dont ils sont interfacés avec le modèle objet de Symfony pour Doctrine.

Grâce à cet outil Profiler de Symfony, dans l’environnement de développement, vous pouvez ainsi, à tout moment, aller approfondir les détails sur tout l’historique des requêtes précédentes de l’application, pour déboguer, ou mieux comprendre le fonctionnement de l’application.

2.7. TODO Étape 1-f : Compréhension sommaire de l’algorithme de gestion du formulaire

Le générateur a créé pour nous des méthodes gérant les requêtes GET et POST et la génération des formulaires.

Examinez dans votre IDE le code du contrôleur src/Controller/PasteController.php qui a été généré par l’assistant, qui correspond à ce qui a été présenté en cours magistral (une version un peu plus sophistiquée, certes).

  1. Regardez le code des méthodes index() et show(), et en parallèle les gabarits Twig templates/paste/index.html.twig et templates/paste/show.html.twig.

    Vous ne devriez pas être trop dépaysés, par rapport à ce qu’on a utilisé jusqu’à présent dans TodoController.php.

  2. Consultez maintenant le code de la méthode PasteController::new(), qui gère le formulaire de création de nouvelles Pastes. Il correspond à celui qu’on a présenté dans le cours.
  3. Ajoutez des éléments de traces avec la fonction dump() pour tester et comprendre les différents éléments de ce code, aux différentes étapes de l’algorithme de traitement des soumissions : les données soumises, les entités du modèle. Vous devez retrouver parmi ces éléments ce que vous avez vu précédemment dans les outils du profiler Symfony

À ce stade, l’application fonctionne de façon interactive, et sauvegarde les opérations CRUD sur les Pastes, en base de données.

Le code généré est modifiable, si besoin, par exemple pour ajouter des opérations pour la gestion de règles métier.

Il n’est pas indispensable de comprendre, à ce stade, l’ensemble complet des opérations effectuées dans ce code, notamment pour la modification et la suppression, car les générateurs réalisent une version assez optimisée, et qu’on pourrait écrire de façon un peu différente, et peut-être moins compacte, donc plus facilement compréhensible.

Nous détaillerons le fonctionnement complet des formulaires dans une séance ultérieure du cours.

2.8. TODO Étape 1-g : Modification du look dans les formulaires générés

Les gabarits générés n’utilisent probablement pas votre structure de gabarits et de blocs. Il va falloir faire quelques ajustements pour récupérer la mise en page standard de l’application.

2.8.1. Utilisation de votre hiérarchie de gabarits

Vérifiez dans l’outil Twig de la barre d’outils de Symfony, comment sont compilés les gabarits des pages relatives aux Pastes.

Si vous examinez attentivement la construction du gabarit paste/index.html.twig, vous constatez qu’il surcharge en fait le bloc Twig body. C’est le comportement par défaut du générateur de code make:crud de Symfony.

A priori, ça fonctionne immédiatement bien avec notre structure définie dans le gabarit de base base.html.twig.

Si jamais vous avez fait évoluer votre gabarit de base, vous auriez par exemple fait le choix d’inclure une section main au sein du bloc body, à charge pour chaque page de surcharger ce block main (vérifiez votre todo.html.twig). Il faudrait alors, dans un tel cas, plutôt qu’on surcharge le bloc interne main. Si nécessaire, il faudrait donc modifier en conséquence les gabarits des Pastes générés par le make:crud, afin de surcharger plutôt ce bloc main, et utiliser ainsi la bonne mise en forme.

2.8.2. Utilisation du style de formulaire Bootstrap

L’assistant générateurs de code make:crud génére des boutons de validation de formulaires qui ne s’affichent pas de façon optimale par défaut, quand on utilise Bootstrap.

Corrigeons cela :

  1. Configurez le thème des formulaires Twig comme étant Bootstrap 5 : modifiez le fichier config/packages/twig.yaml pour ajouter l’entrée form_themes (attention à l’indentation du format YAML) :

    twig:
        ...
        form_themes: ['bootstrap_5_layout.html.twig']
    
  2. Rechargez la page de création de nouvelle paste, et vérifiez que les champs de formulaire prennent bien maintenant toute la largeur utile de la page
  3. Modifiez, si besoin, le gabarit templates/paste/new.html.twig si ce n’était pas déjà fait, pour y surcharger le bloc main plutôt que le bloc body (cf. plus haut).

2.8.3. Modification du look des boutons

Vous pouvez donc modifier le code des gabarits générés pour que les boutons ressemblent à ceux qu’attend Bootstrap, en vous inspirant des exemples fournis dans la documentation.

  1. Chargez le code du gabarit new.html.twig, dans l’IDE. Remarquez qu’il inclut un autre gabarit, dans lequel est situé le code du formulaire proprement dit :

    {{ include('paste/_form.html.twig') }}
    

    Attention à la syntaxe : il s’agit là d’inclure le code d’un autre gabarit, et non de surcharger les valeurs d’un autre gabarit, comme pour {% extends 'base.html.twig' %}

  2. Modifiez le code du formulaire _form.html.twig pour changer l’apparence du bouton de validation du formulaire, par exemple avec :

    <button class="btn btn-primary">...</button>
    
  3. Vérifiez que le formulaire dans la page de création est bien impacté, mais aussi celui dans la page de modification, qui inclut elle aussi le même sous-formulaire _form.html.twig.

    En effet, créer ou modifier des données nécessite à peu près les mêmes champs, dans la plupart des applications simples. C’est donc le comportement choisi par les concepteurs du générateur de CRUD : les deux gabarits de base incluent le même sous-gabarit de fomulaire.

  4. Observez l’intitulé du bouton, qui est tantôt « Save », tantôt « Update« .

    Cela correspond en principe à la valeur qui est présente dans la balise <button>. Ici, on trouve, dans le gabarit :

    <button class="btn btn-primary">{{ button_label|default('Save') }}</button>
    

    Cela signifie que si une variable button_label est fournie au gabarit, alors on s’en sert, et sinon, on prend comme valeur par défaut 'Save'.

  5. Vérifiez le code dans edit.html.twig. Remarquez la définition de button_label, à la volée, lors de l’inclusion du sous-formulaire _form.html.twig.

    Vous voyez encore une fois qu’à travers Twig on dispose d’un mécanisme puissant pour factoriser les instructions et éviter de dupliquer le code, tout en permettant des particularisations.

3. TODO Étape 2 : Ajout d’une gestion CRUD pour les tâches

L’objectif de cette séquence est de mettre en œuvre des fonctions permettant de modifier les tâches via l’interface Web

Vous allez mettre en œuvre le reste des fonctions CRUD (Create Retrieve Update Delete) pour l’entité Todo du modèle applicatif. La consultation (Retrieve) fonctionne déjà, et vous vous attaquez maintenant aux opérations en modification.

Vous vous inspirerez du code de gestion des formulaire obtenu à l’aide des makers pour les Paste.

Pour mettre en œuvre la soumissions de données via les formulaires, pour la création ou la modification, il y a un certain nombre d’éléments à ajouter dans le code, à la fois au niveau des gabarits Twig que du code PHP. Il faut correctement calibrer les interactions entre les différentes couches de l’application : requêtes HTTP GET et POST, détails de HTML, subtilités du code PHP ou Twig…

Les opérations nécessaires pour mener à bien cette étape ne sont pas décrites in extenso dans le présent support.

Il y a besoin d’être assez méticuleux sur le renommage, donc soyez attentifs aux opérations que vous effectuez.

3.1. TODO Étape 2-a : Ajout de la création des tâches

On commence par la première fonction du CRUD : la création. On va tout simplement recopier les éléments correspondants générés par le maker pour les Pastes, dans le code gérant les Todo.

Procédez aux étapes suivantes, tout en testant au fur et à mesure vos modifications dans l’application :

  1. Ajout d’une classe TodoType définie dans le fichier src/Form/TodoType.php :
    • recopiez, dans votre IDE, le fichier PasteType.php et collez-le en tant que TodoType.php
    • dans TodoType.php, modifiez le code pour renommer toutes les occurrences de la chaîne « Paste » en « Todo » (resp. « paste » en « todo ») : utilisez pour cela l’outil syntaxique « Edit / Find/replace »
    • modifiez la liste des champs ajoutés dans le constructeur de formulaire dans la méthode TodoType::buildForm, pour refléter les attributs de Todo :
      • title ;
  2. Ajout d’une méthode new() dans TodoController :
    • copiez-collez le code de la méthode Paste::new pour l’ajouter dans Todo

      Attention, placez cette méthode new() avant la méthode show() dans le fichier source, ou bien, modifiez la définition du routage de show(), pour spécifier le type de l’argument id attendu :

      #[Route('/{id}', name: 'todo_show', requirements: ['id' => '\d+'], methods: ['GET'])]
      public function show(Todo $todo): Response
      {
          // ...
      

      Ceci permet d’éviter une confusion dans le routage, pour ne pas essayer de charger la tâche d’identifiant « new » si on emprunte la route /todo/new.

    • modifiez toutes les occurrences de « Paste » en « Todo » (resp. « paste » en « todo »).

      Notez qu’Eclipse possède une fonction utile pour renommer une variable dans ses différentes occurrences dans le corps d’une méthode : positionnez le curseur dans la première chaîne $paste et sélectionnez le menu du bouton droit « Refactor / Rename ». Au fur et à mesure de la saisie du nouveau nom, vous voyez les autres occurrences de la variable être renommées simultanément. Cet outil est capable de reconnaître la sémantique du langage PHP pour agir au bons endroits, là où un outil syntaxique risquerait de tout casser.

      Certains autres IDE proposent une fonction similaire.

      Pensez à renommer les noms de gabarits ou de routes, qui ne sont que des chaînes de caractère, et pas des variables. Ils ne sont donc pas concernées par l’outil de renommage sémantique, qui ne s’applique qu’aux noms de classes, d’attributs ou de méthodes.

    • ajoutez les use manquants éventuels pour les classes inconnues jusqu’alors dans ce fichier (Request, …)
  3. Ajout des gabarits du formulaire de création de nouvelle tâche :
    • recopiez les deux gabarits new.html.twig et _form.html.twig depuis templates/paste/ vers templates/todo/
    • renommez, dans templates/todo/new.html.twig toutes les occurrences de « Paste » en « Todo » (resp. « paste » en « todo »)
  4. Testez que le formulaire de création de nouvelle tâche fonctionne.

    Résolvez les éventuelles erreurs liées aux attributs manquants signalées par Doctrine.

  5. Ajoutez le lien de création, dans la page de liste des tâches, comme ça a été fait pour les Pastes

Vous constatez que pour obtenir un ensemble de pages parfait, il y a énormément de travail à faire sur ce genre de petits détails.

Ici, il ne s’agit pas d’obtenir une application Symfony « parfaite », car vous n’êtes probablement pas destinés à devenir des développeurs professionnels… la perfection peut attendre un peu !

Vous venez de faire un grand nombre d’opérations de mise au point un peu fastidieuses, pour reprendre la gestion des formulaires, telle que générée par le maker make:crud et l’ajouter dans un contrôleur existant.

Le travail aurait été à peu près aussi difficile, s’il s’était agit de recopier les exemples de la documentation Symfony.

Nous vous avons invité à faire toutes ces opérations, certes fastidieuses, pour qu’elles vous donnent l’occasion de tester le fonctionnement des formulaires et de la mécanique sous-jacente.

Maintenant que vous savez le faire, et que vous voyez l’effort que ça représente, vous mesurez probablement encore plus l’utilité d’un maker comme make:crud.

À l’avenir (dans votre projet), dans un tel cas, il vaut probablement mieux supprimer l’ancienne classe contrôleur et les anciens gabarits, et regénérer une nouvelle classe avec le maker à la place.

3.2. Étape 2-b : Customisation des widgets de dates (optionnel)

Vous pouvez éventuellement sauter cette étape, pour ne pas perdre trop de temps. Vous pourrez y revenir ultérieurement si besoin.

Procédez comme ci-dessus , en recopiant le reste des méthodes et gabarits, depuis Paste, pour ajouter les fonctionnalités CRUD restantes des Todo (modification et suppression) :

  1. la méthode de modification d’une tâche, son gabarit et les liens d’édition des tâches dans la liste des tâches (à côté du lien de consultation);
  2. la méthode de suppression d’une tâche ainsi que son gabarit, qui sont nécessaires au fonctionnement de l’édition.
  3. les liens de modification et de suppression dans le formulaire de consultation d’une tâche

3.3. Étape 2-c : Customisation des widgets de dates (optionnel)

Vous pourriez ensuite customiser le rendu de certains éléments dans les formulaires, mais vous n’avez probablement pas le temps de rentrer dans tous les détails.

Ainsi, pour afficher les champs de saisie des dates, plutôt que d’afficher le détail de chacun des sous-formulaires des sous-éléments constituant une date de la classe DateTime PHP, on peut afficher un élément de formulaire HTML5 et laisser le navigateur faire le job. Vous pouvez ainsi modifier le « widget » de la façon suivante (inspirée des indications de la documentation Symfony) :

use Symfony\Component\Form\Extension\Core\Type\DateType;

//...

  public function buildForm(FormBuilderInterface $builder, array $options): void
  {
    $builder
        // ...
        ->add('created', DateType::class, [
              'widget' => 'single_text'])
        ;
    // ...

4. Étape 3 : Ajout de règles de gestion (optionnel)

L’objectif de cette dernière séquence optionnelle est d’approfondir la compréhension du fonctionnement des algorithmes de gestion des formulaires, en ajoutant des règles de gestion par rapport à la cohérence des données.

Le fonctionnement des méthodes CRUD, tel qu’il est généré par les assistants générateurs de code est assez basique. Il permet de faire des créations, modifications ou suppressions « brutes », sans gestion particulière.

Jusqu’ici, il s’agit principalement de fournir une interface Web pour interagir avec la base de données sous-jacente. Mais s’il ne s’agit que de cela, on avait aussi la possibilité d’utiliser le backoffice via EasyAdmin. Or dans les vraies applications, en général, tous les attributs stockés dans la base de données ne sont pas modifiables par tous les utilisateurs. On doit ainsi respecter des règles de gestion applicatives, qui répondent à des besoins « métiers ».

Il faut donc bien travailler dans le frontoffice avec des pages dont le contenu est customisable à loisir, et où le comportement des formulaires peut être relativement complexe. Il faudra donc coder ces algorithmes précisément, notamment dans les contrôleurs Symfony, et mettre ainsi en place le fameux HATEOS vu en cours.

Nous allons voir comment coder ce genre de règles dans le code PHP de notre application.

4.1. Règles de gestion de la gestion des tâches

Dans notre application fil-rouge, on souhaite ainsi que la gestion des tâches permise par les fonctions de modification des données obéisse à certaines règles :

  • les dates de dernière modification des tâches (todo:updated) sont non modifiables par l’utilisateur dans les formulaires, et mises à jour automatiquement lorsqu’une modification est effectuée sur la tâche (création ou modification)
  • une tâche nouvellement créée :
    • ne peut être créée comme déjà terminée. Il faudra la modifier ultérieurement pour changer son statut (todo:completed)
    • sa date de création (todo:created) est définie automatiquement lors de son ajout dans la base de données, et ne sera plus modifiable ultérieurement

4.2. Mise en œuvre

Modifiez le code pour mettre en œuvre ces règles. Il s’agit de modifier deux élements :

  1. le gestionnaire de formulaire TodoType (dans src/Form/TodoType.php).

    Il est utilisé aussi bien pour le formulaire de création d’une nouvelle tâche que pour une tâche modifiée.

    Vous allez ajouter une option à son comportement de façon à ce qu’il affiche ou non certains champs selon que la tâche est nouvelle, ou modifiée.

    1. Ajoutez-lui une option, via la méthode configureOptions(), nommée task_is_new de type booléen, dont la valeur par défaut est fausse :

      public function configureOptions(OptionsResolver $resolver)
      {
        $resolver->setDefaults([
                'data_class' => Todo::class,
                'task_is_new' => false
        ]);
        $resolver->setAllowedTypes('task_is_new', 'bool');
      }
      
    2. Modifiez le comportement de la création du formulaire, dans buildForm() pour ajouter ou non certains champs, selon qu’ils doivent être inclus dans le formulaire, en fonction de la valeur de task_is_new :

      public function buildForm(FormBuilderInterface $builder, array $options)
      {
        // ...
        $builder->add(...);
      
        if(! $options['task_is_new'] ) 
        {
                $builder->add(...);
        }
        // ...
      
  2. le contrôleur TodoController :
    1. Modifiez le code dans la méthode new(), qui appelle la génération du formulaire, pour passer en argument un tableau d’options, qui contient la valeur de l’option task_is_new, qui doit alors être vraie :

      public function new(Request $request): Response
      {
        $todo = new Todo();
        ...
      
        $form = $this->createForm(TodoType::class, $todo,
                ['task_is_new' => true]
                );
      

      Comme le code de edit() n’est pas changé, les champs présents dans le formulaire de création et de modification sont donc gérés différemment, en fonction de la valeur par défaut de l’option task_is_new.

      Vérifiez que la création fonctionne toujours, et si besoin, définissez une valeur par défaut d’un des attributs.

      Testez l’application ainsi modifiée, et vérifiez que les formulaires de création d’une nouvelle tâche, et de modification d’une tâche existante contiennent des champs différents.

    2. Modifiez le code de gestion des formulaires soumis, pour gérer les attributs dont la valeur doit être calculée. Inspirez-vous de cet example :

      public function edit(Request $request, Todo $todo, EntityManagerInterface $entityManager): Responsepublic function edit(Request $request, Todo $todo): Response
      {
        $form = $this->createForm(TodoType::class, $todo);
        $form->handleRequest($request);
      
        if ($form->isSubmitted() && $form->isValid()) {
      
                // Ici la liste des valeurs calculées, juste avant la sauvegarde
                // dans la base de données
                $todo->setUpdated(new \DateTime());
      
                $entityManager->flush();
      
                        return $this->redirectToRoute('app_todo_index', [], Response::HTTP_SEE_OTHER);
        }
        ...
      

Testez l’application ainsi modifiée, et vérifiez que les dates sont mises à jour correctement dans la base de données.

Vous voyez, à travers la mise en œuvre d’une simple règle de gestion, comment la classe gestionnaire de formulaire TodoType prend en charge une partie du comportement. Encore un élément de l’ensemble des outils de Symfony qui permet d’organiser le code, en objet, en « améliorant » (relativement) la maintenabilité du code, pour des applications réelles.

5. Évaluation

Vous avez expérimenté avec les éléments suivants :

  • utiliser les générateurs de Contrôleurs CRUD Symfony pour mettre en place du code gérant les formulaires
  • observer le fonctionnement de la gestion des soumissions de données

6. Ensuite

D’ici la fin de la séance, et d’ici le prochain cours magistral, vous allez travailler en autonomie pour préparer les phases suivantes du projet, en vous appuyant sur le support de travail en autonomie 7-8.

Auteur: Olivier Berger (TSP)

Created: 2023-10-15 Sun 15:24

Validate