Architecture d'un site web : manipulation des données
Notes de cours
- Manipulation des données dans l’architecture MVCR
- Problèmes liés à la manipulation des données
- Architecture pour CRUD de base
- Démonstration sur le site des couleurs (archive)
- POST-redirect-GET
- Feedback
Travail personnel
Objectifs
L'exercice est une application directe de l'architecture présentée en cours pour la manipulation des données.
NB: il faut avoir terminé les parties obligatoires du TP précédent avant de commencer celui-ci.
Exercice 1 — Manipulation des données dans MVCR #
On va continuer le site sur les animaux fait au TP précédent, en permettant aux internautes d'ajouter leurs propres animaux à la base.
Une nouvelle fois, l'énoncé est long mais c'est parce que l'exercice est très guidé ; il n'y a pas de difficulté particulière. Si vous avez des doutes, jetez un œil à l'exemple des couleurs qui a été en partie présenté en cours (résultat final, archive du code). Ne faites pas de copier-coller, réécrire est beaucoup plus efficace pour l'apprentissage. De plus, l'exercice est (comme celui du TP précédent) très incrémental : j'essaie de justifier chaque ajout de complexité dans l'architecture. On ne prendra pas le chemin le plus direct vers la version finale, ne soyez pas surpris si ça ne ressemble pas immédiatement à l'exemple présenté en cours.
Préliminaires
Avant de commencer cet exercice, il faut avoir terminé le TP précédent, au moins jusqu'à la partie « Stockage » incluse. Si vous avez fait la partie « Compléments », il faudra adapter un peu certaines questions, mais vous devriez vous y retrouver.
Ajout d'un menu
Ajouter un attribut $menu
à la vue,
qui contient une liste de couples URL-texte.
Cette liste doit être utilisée dans le squelette pour afficher un menu
en haut de la page.
Le menu doit contenir (pour l'instant) un lien vers la page d'accueil et un vers la liste des animaux.
Persistance des données
La pseudo-base (non modifiable) qu'on a utilisée ne suffit plus pour cet exercice, puisqu'on veut pouvoir y rajouter des animaux : il faut que les modifications soient persistantes. Pour cela, on pourrait stocker nos animaux dans une BD MySQL, mais pour gagner du temps on va choisir une solution plus simple (au moins au début), celle d'enregistrer notre tableau en session. Ça n'a bien sûr rien à voir avec une vraie persistance des données : les animaux créés ne seront pas accessibles à tous les internautes !
- Ajoutez les méthodes suivantes à votre interface
AnimalStorage
, en documentant correctement les méthodes avec des commentaires :create(Animal $a)
ajoute à la base l'animal donné en argument, et retourne l'identifiant de l'animal ainsi créé.delete($id)
supprime de la base l'animal correspondant à l'identifiant donné en argument ; retournetrue
si la suppression a été effectuée etfalse
si l'identifiant ne correspond à aucun animal.update($id, Animal $a)
met à jour dans la base l'animal d'identifiant donné, en le remplaçant par l'animal donné ; retournetrue
si la mise à jour a bien été effectuée, etfalse
si l'identifiant n'existe pas (et donc rien n'a été modifié).
AnimalStorageStub
: elles doivent simplement jeter une exception (elles n'ont pas de sens sur cette implémentation). - On vous fournit la classe
AnimalStorageSession
, qui implémenteAnimalStorage
en utilisant une session PHP (les animaux créés sont donc locaux à la session de l'internaute, et disparaîtront à la fin de la session). Télécharger le fichier correspondant dans cette archive, et le placer danssrc/model
. - Modifier
site.php
pour que le système utilise unAnimalStorageSession
à la place de l'AnimalStorageStub
. Attention, il faut lancer une session avant de créer l'instance deAnimalStorageSession
, mais après avoir inclus la classe (carsession_start
désérialise les animaux en session, et pour ça PHP a besoin de connaître la classe). - Vérifier que votre site fonctionne toujours comme avant.
Affichage debug
Ajouter la méthode suivante à la vue :
public function prepareDebugPage($variable) { $this->title = 'Debug'; $this->content = '<pre>'.htmlspecialchars(var_export($variable, true)).'</pre>'; }Elle va faciliter le debug en nous permettant d'afficher le contenu d'une variable. La tester depuis le routeur en lui faisant afficher divers objets.
Avant de passer à la suite, faites un commit du code courant avec comme message « Préliminaires TP 09 ».
Plus précisément,
ouvrez un terminal (local) dans exoMVCR
(pas dans src
!) et exécutez les commandes suivantes :
git add . git commit -m "Préliminaires" git pushSi vous faites ensuite un
git status
, Git devrait vous dire que « la copie de travail est propre ». Si ce n'est pas le cas, il y a eu un problème, voyez avec votre chargé·e de TP.
Création d'un nouvel animal
- La page avec le formulaire de création d'un animal sera (par exemple) à l'URL
site.php?action=nouveau
, et la page destinée à recevoir les données sera à l'URLsite.php?action=sauverNouveau
. Ajouter au routeur des méthodesgetAnimalCreationURL()
etgetAnimalSaveURL()
qui renvoient ces URLs. - Ajouter une méthode
prepareAnimalCreationPage()
à la vue. Elle doit afficher un formulaire de création d'un animal, avec trois champs texte :nom
,espece
etage
. Le formulaire doit envoyer les données enPOST
à l'URL donnée par legetAnimalSaveURL()
du routeur. - Ajouter deux méthodes au contrôleur :
-
createNewAnimal()
, qui demande à la vue de préparer le formulaire -
saveNewAnimal(array $data)
, qui se contente pour l'instant d'afficher son argument grâce àprepareDebugPage
.
-
- Modifier le routeur pour qu'il analyse le paramètre
action
de l'URL, et qu'il appellecreateNewAnimal()
si le paramètre estnouveau
, etsaveNewAnimal($_POST)
si le paramètre estsauverNouveau
. Cela permet de tester que tout fonctionne pour le moment. - Dans la méthode
saveNewAnimal
, au lieu d'afficher le tableau passé en argument, l'utiliser pour créer une instance de Animal et l'afficher avecprepareAnimalPage
. Ça devrait fonctionner, mais l'animal n'est pas ajouté à la base (on peut le constater en affichant la liste des animaux). - Faire en sorte que
saveNewAnimal
ajoute le nouvel animal à la base avant de le passer à la vue. - Vérifier que tout marche bien : on doit pouvoir créer des animaux qui s'ajoutent à la liste.
Avant de passer à la suite, faites un commit du code courant avec comme message « Création ». (voir les instructions ici (adapter le message de commit)).
Validation
Les internautes peuvent maintenant ajouter leurs animaux favoris à votre site. Cependant, iels peuvent aussi envoyer des données incorrectes, par exemple en laissant les champs vides. On va commencer par faire une validation de base, et on gérera les données incomplètes dans la section suivante.
- Modifier
saveNewAnimal()
pour qu'il ne soit pas possible d'ajouter un animal dont le nom ou l'espèce soient vides ou dont l'âge ne soit pas un nombre positif. On pourra afficher une page d'erreur par exemple. - Cette solution n'est pas idéale : en cas d'erreur, l'internaute perd ce qu'il ou elle a entré.
Il est bien plus ergonomique de lui redonner la main sur le formulaire avec les champs tels qu'ils ont été remplis. Pour cela, en cas d'erreur, le contrôleur va redonner le tableau qu'il a reçu
à la méthode
prepareAnimalCreationPage
de la vue, pour qu'elle remplisse les champs du formulaire avec. Faire les modifications nécessaires. (Attention notamment à ce que tous les appels àprepareAnimalCreationPage
soient corrects.) - C'est mieux, mais maintenant il est moins clair pour l'internaute
qu'une erreur est survenue. D'autre part, la raison pour laquelle
les données étaient invalides n'est pas forcément évidente : il est nécessaire
d'en informer l'internaute. Faire en sorte que le contrôleur
passe une chaîne
$error
àprepareAnimalCreationPage
qui se chargera de l'afficher. La chaîne seranull
s'il n'y avait pas d'erreur, et contiendra sinon une explication sur l'erreur. - Vérifier que tout fonctionne, c'est-à-dire le cas normal et tous les cas d'erreur.
- Essayer d'ajouter un animal qui aurait pour espèce «
<script>alert('coucou')</script>
» (ou mettez cette chaîne de caractères dans un des champs du formulaire qui se retrouvera affiché sur la page de l'animal). Que se passe-t-il ? Que faut-il faire pour empêcher ça ?
Avant de passer à la suite, faites un commit du code courant avec comme message « Validation ». (voir les instructions ici (adapter le message de commit)).
Gestion des données incomplètes
Il y a deux problèmes avec notre manipulation des données. D'abord, c'est le contrôleur qui décide quelles données sont valides (alors que ça concerne le modèle). D'autre part, il y a duplication du nom des champs de formulaire, dans la vue et dans le contrôleur. C'est moins gênant que pour les paramètres d'URL, car ils sont moins visibles, mais ils font cependant partie de l'interface « externe » du site (pas comme les noms des variables PHP, par exemple), et à ce titre ils se doivent d'être relativement faciles à changer.
On va gérer ces deux problèmes à la fois en ajoutant une nouvelle classe au modèle,
AnimalBuilder
, qui représente un animal en cours de manipulation (création ou modification) dans l'application,
et qui permet de construire l'instance de Animal
correspondante.
- Créer cette classe et lui donner un attribut
$data
, passé en argument à son constructeur, et un attribut$error
, initialisé ànull
. Ajouter un accesseur pour chacun de ces attributs. - Modifier la méthode
saveNewAnimal
du contrôleur pour qu'elle crée une instance deAnimalBuilder
avec le tableau$data
qu'elle a elle-même reçu du routeur. - Ajouter une méthode
createAnimal()
dansAnimalBuilder
, qui crée une nouvelle instance deAnimal
en utilisant l'attribut$data
(déplacer le code depuis le contrôleur). - Ajouter une méthode
isValid()
dansAnimalBuilder
, qui vérifie que les données de son attribut$data
sont correctes (déplacer à nouveau le code depuis le contrôleur). Si elles ne le sont pas, placer la chaîne expliquant l'erreur dans l'attribut$error
. - Modifier la méthode
prepareAnimalCreationPage
pour qu'elle ne prenne comme argument qu'une instance deAnimalBuilder
, et l'utilise pour pré-remplir les champs et pour afficher l'erreur éventuelle. - Modifier
saveNewAnimal
pour utiliser l'instance deAnimalBuilder
. Vérifier que tout marche toujours (normalement non : il faut modifier l'appel initial àprepareAnimalCreationPage
, qui doit récupérer unAnimalBuilder
vide ; qui doit s'occuper de le construire ?) - Il ne reste plus qu'à « cacher » les noms des champs.
Ajouter à
AnimalBuilder
des constantesNAME_REF
,SPECIES_REF
etAGE_REF
, qui contiennent respectivementnom
,espece
etage
. Utiliser ces constantes dans la classe en question, et dansprepareAnimalCreationPage
à la place des noms de champ codés « en dur ». - Vérifier que tout marche toujours, puis changer les noms des champs (les passer en majuscules par exemple).
Normalement, vous ne devriez avoir à modifier que les constantes de
AnimalBuilder
, et tout doit toujours marcher (et les noms des champs modifiés se verront dans le HTML).
Avant de passer à la suite, faites un commit du code courant avec comme message « AnimalBuilder ». (voir les instructions ici (adapter le message de commit)).
POST-redirect-GET
Normalement, lors de la création d'un nouvel animal,
le contrôleur appelle le prepareAnimalPage
de la vue.
Comme vu en cours, ce n'est pas une bonne chose : on va plutôt
demander au client de faire une nouvelle requête, en GET
,
vers la page de l'animal nouvellement créé.
-
Ajouter une méthode
POSTredirect($url, $feedback)
au routeur, qui envoie une réponse HTTP de type303 See Other
demandant au client de se rediriger vers l'URL passée en argument (le paramètre$feedback
est pour l'instant ignoré). - Ajouter une méthode
displayAnimalCreationSuccess($id)
à la vue, qui utilise la méthode créée ci-dessus pour rediriger le client vers la page de l'animal dont l'identifiant est passé en paramètre (passer une chaîne quelconque comme feedback pour l'instant). Dans le contrôleur, lorsqu'un nouvel animal a été correctement ajouté, appeler cette nouvelle méthode plutôt queprepareAnimalPage
. - Tester. Vérifier notamment que l'actualisation de la page fonctionne correctement après ajout d'un nouvel animal.
Avant de passer à la suite, faites un commit du code courant avec comme message « Redirection après POST ». (voir les instructions ici (adapter le message de commit)).
Feedback
On voudrait donner un feedback à l'internaute, pour l'informer sur le résultat de ses actions (succès ou erreur).
- Ajouter un attribut
$feedback
à la vue, passé à son constructeur. Le squelette devra afficher le contenu de cet attribut quelque part en haut de la page. - Modifier le routeur pour qu'il passe la chaîne
"Test"
comme feedback lorsqu'il construit la vue. Vérifier que ça fonctionne. - À présent, on va utiliser une variable de session
$_SESSION['feedback']
pour passer le feedback d'une requête à l'autre. Commencer une session au début dumain
du routeur, utiliser la variable de session$_SESSION['feedback']
pour construire la vue, puis supprimer le contenu de cette variable (le feedback n'est affiché qu'une seule fois). - Modifier la méthode
POSTredirect
pour qu'elle mette dans$_SESSION['feedback']
le contenu de son paramètre$feedback
, avant la redirection. - Tester (et mettre un message de feedback significatif).
Avant de passer à la suite, faites un commit du code courant avec comme message « Feedback ». (voir les instructions ici (adapter le message de commit)).