TP n°7 - Implémentation d’un CRUD sur ToDo
Générateurs de formulaires CRUD avec Symfony
Table des matières
- 1. Introduction
- 2. Étape 1 : Mise en place de la séance
- 3. Étape 1 : Ajout de fonctions de soumission de données à
ToDo
- 3.1. Principe des générateurs de code CRUD Symfony
- 3.2. TODO Étape 1-a : Ajout d’une nouvelle entité
Paste
- 3.3. TODO Étape 1-b : Mise à jour du schéma de la base de données
- 3.4. TODO Étape 1-c : Ajout d’un controleur CRUD pour les Pastes
- 3.5. TODO Étape 1-d : Tests de l’application modifiée
- 3.6. TODO Étape 1-e : Compréhension des requêtes mises en jeu dans un formulaire de création
- 3.7. TODO Étape 1-f : Compréhension sommaire de l’algorithme de gestion du formulaire
- 3.8. Étape 1-g : Modification du look dans les formulaires générés
- 4. Étape 2 : Ajout d’une gestion CRUD pour les tâches
- 5. Ajout de règles de gestion (optionnel)
- 6. Évaluation
- 7. Ensuite
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 : Mise en place de la séance
2.1. TODO Préparation du répertoire de travail
Vous allez dupliquer l’arborescence du projet PHP obtenu à la fin de la séance précédente, pour travailler sur une nouvelle version.
Vous pourrez ainsi faire évoluer le code, et revenir en arrière si besoin, en comparant l’état à la fin de la séance prédente, avec l’état courant.
Effectuez les opérations suivantes pour dupliquer le dossier :
cd "$HOME/CSC4101" cp -r tp-05 tp-07 cd tp-07 cd todo-app
Ensuite, dans ce nouveau projet, on va réinitialiser les rouages du cadriciel Symfony avec Composer :
cd "$HOME/CSC4101/tp-07/todo-app" rm -fr composer.lock symfony.lock var/cache/ vendor/ .project symfony composer install
Confirmez la génération de fichiers pour Docker (on ne s’en servira pas tout de suite, mais pourquoi ne pas les avoir… le rôle de Docker ne sera pas évoqué en cours, mais vous pouvez en parler à vos encadrants de TP).
Cette étape est nécessaire car Symfony s’appuie sur une mécanique
sophistiquée de mise en cache du code de
l’application (dans var/cache/
), pour assurer des performances
maximales. Malheureusement, si on duplique un projet, une partie de ce
cache a tendance à devenir incohérente (présence de chemins d’accès « en dur », etc.).
Il est donc préférable de réinitialiser le projet radicalement : à
un état le plus « propre » possible. On fait ensuite réinstaller par Composer
les bibliothèques du cadriciel Symfony (installées dans vendor/
), et
reconfigurer certains fichiers de configuration associés.
2.2. TODO Chargement du nouveau projet dans l’IDE
Vous allez travailler sur un nouveau projet dans le workspace dans l’IDE Eclipse, importé depuis ce nouveau répertoire de travail.
Pour les utilisateurs de l’IDE Eclipse, supprimez les anciennes infos du projet Eclipse :
cd "$HOME/CSC4101/tp-07/todo-app" rm -fr .project .settings/
Importez dans votre IDE cette nouvelle version du projet Symfony Todo sur laquelle vous allez maintenant travailler.
Dans Eclipse vous pouvez importer ce nouveau projet dans le même workspace que le précédent, mais avec un nom distinctif (par exemple « todo-tp-7 »).
Si besoin, vous pourrez comparer le code des deux projets « todo-tp-7 » et « todo-tp-5 ». Mais pour l’instant, et pour éviter de vous mélanger, nous vous conseillons de fermer le projet de la séance précédente (menu Project / Close …).
3. É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.
3.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
)
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 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
).
Il existe un autre générateur de code relativement similaire, pour le
module EasyAdmin, qui génère aussi des fonctionnalités CRUD pour une
entité du modèle des données (make:admin:crud
). Normalement, vous ne
l’utilisez pas dans le cours, dans cette édition. Mais attention à ne
pas confondre les deux, au cas où…
3.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 allons travailler sur une nouvelle entité du modèle qu’on va
ajouter à l’application ToDo : l’entité Paste
(collage).
Elle est censée permettre de garder dans notre application Web une trace de copiés-collés (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 :
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
- Rafraîchissez le projet dans votre IDE, et vérifiez ensuite le code généré dans
src/Entity/
.
3.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 Doctrine 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 :
- suppression de la base de données :
symfony console doctrine:database:drop --force
- recréation de la base de données :
symfony console doctrine:database:create
- recréation du schéma de la base de données :
symfony console doctrine:schema:create
- 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 :
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
created: migrations/Version20240828105223.php Success! Review the new migration then run it with symfony console doctrine:migrations:migrate See https://symfony.com/doc/current/bundles/DoctrineMigrationsBundle/index.html
Observez dans votre IDE le script PHP qui vient d’être généré (ici
migrations/Version20240828105223.php
).Vous remarquerez qu’il revient ici à appliquer un
CREATE TABLE
pour la nouvelle entité ajoutée.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]:
- 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
.
3.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 :
Ajoutez un nouveau contrôleur CRUD
PasteController
, pour la classePaste
:symfony console make:crud
The class name of the entity to create CRUD (e.g. BravePuppy): > Paste Choose a name for your controller class (e.g. PasteController) [PasteController]: > Do you want to generate PHPUnit tests? [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/
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.Le code des méthodes
index()
etshow()
générées dansPasteController
devrait vous sembler familier, très similaire à celui que vous avez ajouté dans une séquence précédente dansTaskController
.On va voir le reste des méthodes ci-dessous.
Notez que ce générateur de contrôleur Symfony est différent de celui qu’on avait étudié dans une séquence précédente.
Vous disposez maintenant de deux générateurs de contrôleurs :
make:controller
qui génère un contrôleur de base, presque vide (déjà utilisé précédamment pour l’ajout deTaskController
)make:crud
celui-ci, qui génère le code d’un contrôleur sophistiqué intégrant les fonctionnalités CRUD
À vous de choisir selon les besoins : gérer un CRUD sur une éntité du modèle de données, ou tout écrire à la main dans d’autres cas…
3.5. TODO Étape 1-d : Tests de l’application modifiée
Testez maintenant l’application qui permet maintenant une soumission de données :
- Consultez la liste des nouvelles routes, avec
symfony console debug:router
- Lancez l’application en mode Web comme d’habitude
Testez l’application dans le navigateur derrière les chemins des pastes :
http://localhost:8000/paste/
.Vérifiez que la création fonctionne, et que vous pouvez maintenant ajouter, modifier ou supprimer des données via les fonctions accessibles par les nouvelles routes CRUD.
Les boutons « Save » de validation des formulaires sont assez peu visibles pour l’instant, mais bien actifs.
On corrigera cela plus tard.
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 à nouveau 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
- Vérifiez bien 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.
Excellent. L’assistant make:crud
a généré des formulaires
opérationnels, bien intégrés avec Doctrine. Cool.
3.6. TODO Étape 1-e : Compréhension des requêtes mises en jeu dans un formulaire de création
Dans le fonctionnement des formulaires de création d’entités pastes, la séquence d’interactions HTTP, est la suivante :
- chargement du formulaire HTML de création (1ère requête :
GET
sur/paste/new
) - soumission de données à la validation du formulaire (2ème
requête :
POST
, encore sur/paste/new
) - une fois la soumission traitée par l’application, redirection (automatique opérée
par le navigateur) vers la liste des pastes (nouvelle requête
GET
, cette fois vers/paste/
)
Vérifiez que c’est bien le cas :
dans le navigateur à l’aide du Moniteur réseau dans les outils du développeur Web, observez les requêtes pendant la création. La colonne « Méthode » indique s’il s’agit d’un
GET
ou d’unPOST
:Figure 1 : Trace des requêtes lors de la création dans le navigateur
Observez en particulier 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êteLocation
.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) :
[PHP ] ... [200]: GET /paste/new ... [PHP ] ... [303]: POST /paste/new ... [PHP ] ... [200]: GET /paste/
Observez également les caractéristiques du traitement de la requête
POST
via la barre d’outils Symfony (le Symfony profiler).Dans le coin inférieur droit de la page un popup affiche la dernière requête/réponse correspondant à l’état courant de la page :
Figure 2 : Popup du profiler pour la page courante
Il nous indique les éléments suivants :
- on est bien en train de consulter la page de liste des pastes
(affichée par
PasteController::index
), avec le code de réponse 200. - mais il nous indique aussi que c’est suite à un Redirect from
consécutif au
POST
surapp_paste_new
. À côté, il fournit un lien « (769960) » qui permet de cliquer pour accéder à l’outil des requêtes/réponses du profiler, directement sur cette requête POST précédente.
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 routeapp_paste_new
). Elle avait été traîtée par l’application Symfony qui lui a associé en interne, le jeton de requête (Token) « 769960 », dans cet exemple.- on est bien en train de consulter la page de liste des pastes
(affichée par
Dans l’outil de requêtes / réponses du Profiler Symfony, on retrouve les mêmes infos (plus détaillées) concernant cette dernière requête post-redirection :
Figure 3 : Affichage de la requête dans le Profiler
Remarquez que l’historique des requêtes précédamment traitées par Symfony est accessible derrière le lien « Last 10 ». On y retrouve nos 3 dernières requêtes de gestion du formulaire de création :
Figure 4 : Dix dernières requêtes traitées par l’application
Cliquez sur ce lien de jeton de requête POST (ici « 769960 »).
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êtesGET
:- nom de la méthode du contrôleur invoquée :
PasteController::new
- données du
POST
:content
created
- …
- etc.
Figure 5 : Détails de la requête de soumission de données
- nom de la méthode du contrôleur invoquée :
Cliquez alors sur l’outil « Doctrine »
Vous voyez alors, à la fin, la requête SQL qui a été générée :
START TRANSACTION
INSERT INTO paste (content, created, content_type) VALUES (?, ?, ?) ...
COMMIT
En cliquant sur « View runnable query« on voit une version plus compréhensible de la requête SQL :
INSERT INTO paste (content, created, content_type) VALUES ('Essai', '2024-08-09 10:10:00', NULL);
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 les champs du formulaire sont interfacés avec le modèle objet de l’entité DoctrinePaste
.Figure 6 : Contenu du formulaire de données soumises, associées au modèle de données
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.
3.7. TODO Étape 1-f : Compréhension sommaire de l’algorithme de gestion du formulaire
Le générateur make:crud
a créé pour nous des méthodes qui gèrent les requêtes GET
et
POST
et le fonctionnement 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).
Regardez le code des méthodes
index()
etshow()
, et en parallèle les gabarits Twigtemplates/paste/index.html.twig
ettemplates/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
.- 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. - 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.
3.8. É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.
3.8.1. TODO Utilisation de votre hiérarchie de gabarits
Vérifiez dans l’outil Twig du profiler Symfony, comment sont compilés les gabarits des pages relatives aux Pastes.
Si vous examinez attentivement la construction du gabarit
templates/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 avec notre structure
définie dans le gabarit de base base.html.twig
… mais on a vu à la
séquence précédente qu’on avait un léger souci.
On a en effet fait évoluer le gabarit de base : on a fait le choix d’inclure une section main
au sein du bloc
body
(pour l’ajout de BootStrap). À charge pour chaque page de surcharger ce block main
(vérifiez
votre todo.html.twig
).
Il faut donc qu’on s’aligne, et qu’on surcharge également le bloc
interne main
.
Modifiez en conséquence les gabarits des Pastes générés par
make:crud
, afin de surcharger plutôt ce bloc main
, et utiliser ainsi
la bonne mise en forme.
3.8.2. TODO Utilisation du style de formulaire Bootstrap
Le détail de l’intérieur des formulaires n’est pas spécialement élégant, bien qu’on utilise BootStrap. Corrigeons cela :
Configurez le thème des formulaires Twig comme étant Bootstrap 5 : modifiez le fichier
config/packages/twig.yaml
pour ajouter l’entréeform_themes
(attention à l’indentation du format YAML) :twig: ... form_themes: ['bootstrap_5_layout.html.twig']
- Modifiez, si besoin, le gabarit
templates/paste/new.html.twig
si ce n’était pas déjà fait, pour y surcharger le blocmain
plutôt que le blocbody
(cf. plus haut). - Rechargez la page de création de nouvelle paste, et vérifiez que les champs de formulaire ont maintenant un look différent.
3.8.3. TODO Modification du look des boutons
L’assistant générateurs de code make:crud
a généré des
boutons de validation de formulaires qui ne
s’affichent pas de façon optimale par défaut (comme « Save »), quand on utilise
Bootstrap.
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.
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' %}
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>
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 la création et la modification incluent ce même sous-gabarit de fomulaire.
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'
.Vérifiez le code dans
edit.html.twig
. Remarquez la définition debutton_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.
Nous avons fait le tour du fonctionnement des mécanismes CRUD et de la structure des gabarits associés, mais on n’a pas trop examiné le code PHP du traitement des requêtes dans l’application.
Examinons maintenant plus en détails ce que font les méthodes CRUD de la classe Controller.
4. Étape 2 : Ajout d’une gestion CRUD pour les tâches
L’objectif de cette séquence est de comprendre le fonctionnement des fonctions de gestion des formulaires dans les contrôleurs CRUD.
On souhaite mettre en œuvre le reste des fonctions CRUD (Create Retrieve Update Delete) pour les tâches dans l’application.
La consultation (Read / Retrieve) pour l’entité Todo
du modèle applicatif fonctionne déjà, et on s’attaque maintenant à
l’ajout des opérations en modification.
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…
On ne peut malheureusement pas se servir cette fois du générateur
make:crud
, car notre contrôleur TodoController
existe déjà. Ce
générateur ne sait pas modifier du code de classe controleur
existante.
On va devoir coder « à la main » en s’inspirerant du code de gestion des formulaire obtenu ci-dessus à l’aide du maker pour les Paste.
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 nommage, donc soyez attentifs aux opérations que vous effectuez.
4.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 :
- Ajout d’une classe gestionnaire de formulaire / « type de données »,
TodoType
définie dans le fichiersrc/Form/TodoType.php
:recopiez, dans votre IDE, le fichier
PasteType.php
et collez-le en tant queTodoType.php
Notez qu’Eclipse propose de renommer le nom de la classe
PasteType
enTodoType
pour l’aligner sur le nom du fichier. Pratique !- 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 deTodo
:title
;created
;updated
;
Le reste des attributs pourra être vu plus tard.
- Ajout d’une méthode
new()
dansTodoController
:copiez-collez le code de la méthode
PasteController::new()
pour l’ajouter dansTodoController
.Dans certains contrôleurs, comme l’a fait le maker dans
PasteController
, il faut placer cette méthodenew()
avant la méthodeshow()
dans le fichier source.Cela 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
, si la route « _show » a simplement comme motif de chemin'/{id}'
.Dans notre cas, on avait déjà une définition du routage de
TodoController::show()
, qui spécifie bien le type de l’argumentid
attendu, donc pas de problème de confusion :#[Route('/{id}', name: 'todo_show', requirements: ['id' => '\d+'], methods: ['GET'])] public function show(Todo $todo): Response { // ...
Explication :
requirements: ['id' => '\d+']
permet de ne faire correspondre la route que siid
contient un entier («\d+
» est l’expression rationnelle pour signifier « succession de décimaux »).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
deSymfony\Component\HttpFoundation
,EntityManagerInterface
, …)
- Ajout des gabarits du formulaire de création de nouvelle tâche :
- recopiez les deux gabarits
new.html.twig
et_form.html.twig
depuistemplates/paste/
verstemplates/todo/
- renommez, dans
templates/todo/new.html.twig
toutes les occurrences de « Paste » en « Todo » (resp. « paste » en « todo »)
- recopiez les deux gabarits
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.
- Ajoutez dans la page de liste des tâches (dans
todo/index.html.twig)
le lien de création, comme ça a été fait pour les Pastes
Vous constatez que pour obtenir un ensemble de pages parfait, il y a beaucoup 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 une des opérations de gestion des formulaires, pour ajouter la « création » d’entités à un contrôleur existant.
Mais il reste encore la modification, et la suppression (voir plus loin).
Nous sommes partis du code tel que généré par make:crud
pour les
pastes pour faciliter le copier/coller. Le travail aurait été à peu
près aussi difficile, s’il s’était agit de recopier les exemples de la
documentation Symfony (par exemple depuis Forms).
Nous vous avons invité à faire toutes ces opérations, certes fastidieuses, pour qu’elles vous donnent l’occasion de tester en détail le fonctionnement des formulaires et de la mécanique sous-jacente, à l’aide des outils de mise-au-point présentés plus haut.
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
.
Il resterait maintenant à ajouter le code des méthodes restantes du CRUD :
modification (edit()
) et suppression (delete()
) du contrôleur (voir la
section optionnelle ci-dessous).
À l’avenir (dans votre projet), dans un tel cas, il vaudra probablement mieux supprimer
l’ancienne classe contrôleur et les anciens gabarits, et générer à la place une
nouvelle classe avec le maker make:crud
.
4.2. Ajout des méthodes CRUD restantes dans TodoController (optionnel)
Vous pouvez éventuellement sauter cette étape, pour ne pas perdre trop de temps. Vous pourrez y revenir ultérieurement si besoin.
Il rester à ajouter les fonctionnalités CRUD restantes des Todo (modification et suppression) :
Procédez comme ci-dessus, en recopiant le reste des méthodes et gabarits, depuis Paste :
- 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);
- la méthode de suppression d’une tâche ainsi que son gabarit, qui sont nécessaires au fonctionnement de l’édition.
- les liens de modification et de suppression dans le formulaire de consultation d’une tâche
4.3. Customisation des widgets de dates (optionnel)
Il se peut que vous ayez ajouté les champs de sasie de dates dans TodoType
avec du code de ce type :
use Symfony\Component\Form\Extension\Core\Type\DateType; //... public function buildForm(FormBuilderInterface $builder, array $options): void { $builder // ... ->add('created') // ... ; // ...
L’affichage par défaut n’est pas aussi utilisable que celui des dates de création des pastes :
Figure 7 : Affichage d’un champ DateTime en mise en forme par défaut
En effet, on n’a pas spécifié la façon dont le widget doit afficher le champ.
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), qui se trouve être la même que dans ce que le maker a généré pour les Pastes :
use Symfony\Component\Form\Extension\Core\Type\DateType; //... public function buildForm(FormBuilderInterface $builder, array $options): void { $builder // ... ->add('created', DateType::class, [ 'widget' => 'single_text']) ; // ...
Le résultat est tout de suite mieux :
Figure 8 : Affichage d’un champ DateTime en mise en forme avec un Date picker
Il y a plein de détails, comme ça, sur la façon de construire les différents composants des pages. Malheureusement, il n’est pas possible d’aborder tout cela dans le cours, faute de temps.
Si vous avez vu ça dans un site, il y a probablement une façon de le faire… plus ou moins bien documentée…
5. 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.
Cette section suppose d’avoir terminé la section optionnelle « Ajout des méthodes CRUD restantes dans TodoController » ci-dessus.
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.
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 s’agit de travailler sur le code de 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.
5.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
- ne peut être créée comme déjà terminée. Il faudra la modifier
ultérieurement pour changer son statut (
5.2. Mise en œuvre
Modifiez le code pour mettre en œuvre ces règles. Il s’agit de modifier deux élements :
le gestionnaire de formulaire
TodoType
(danssrc/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.
Ajoutez-lui une option, via la méthode
configureOptions()
, nomméetask_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'); }
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 detask_is_new
:public function buildForm(FormBuilderInterface $builder, array $options) { // ... $builder->add(...); if(! $options['task_is_new'] ) { $builder->add(...); } // ...
- le contrôleur
TodoController
: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’optiontask_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’optiontask_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.
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.
6. Évaluation
Vous avez expérimenté avec les éléments suivants :
- utiliser le générateur de Contrôleurs CRUD Symfony
make:crud
pour mettre en place du code gérant les formulaires - observer le fonctionnement de la gestion des soumissions de données dans HTTP, avec les 3 requêtes et la redirection
- utiliser différents outils du Profiler Symfony pour la mise au point
- comprendre le rôle des méthodes standard qui mettent en œuvre les
fonctions CRUD dans les classes contrôleur :
new()
(pour « C »),index()
etshow()
(pour « R »),edit()
(« U ») et enfindelete()
(« D ») - comprendre le principe de factorisation d’éléments communs à des formulaires, dans des sous-gabarits Twig
- identifier le rôle des classes gestionnaire de formulaires comme
TodoType
qui font le lien avec les attributs des entités Doctrine du modèle de données
7. 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.