Architecture d'un site web : manipulation des données

Licence Informatique 3ème année

Alexandre Niveau — Jean-Marc Lecarpentier

Enseignement des technologies du Web

 

Architecture d'un site web : manipulation des données

Notes de cours

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/objets fait au TP précédent, en permettant aux internautes d'ajouter leurs propres objets à 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 : persistance des données et affichage de debug

Avant de commencer cet exercice, il faut avoir terminé l'exercice d'introduction à l'architecture du TP précédent, au moins jusqu'à la partie « Stockage » incluse. Si vous avez fait (certains morceaux de) la partie « Compléments », il faudra adapter un peu certaines questions, mais vous devriez vous y retrouver.

La pseudo-base (statique) qu'on a utilisée ne suffit plus pour cet exercice, puisqu'on veut pouvoir y rajouter des objets : il faut que les modifications soient persistantes. Pour cela, on pourrait stocker nos objets 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 dans un fichier.

  1. Télécharger le fichier définissant la classe ObjectFileDB (ainsi que la classe FileStore sur laquelle elle s'appuie) dans cette archive (ou faites un copier-coller du code). Le placer dans un répertoire src/lib.
  2. Créer une classe AnimalStorageFile (adapter le nom à vos objets) dans src/model, avec un attribut $db qui contiendra l'instance de ObjectFileDB qui va servir à enregistrer la « base ». Regarder le code de ObjectFileDB, et en particulier la documentation, pour comprendre comment vous allez devoir l'utiliser. Vous pouvez aussi regarder comment fonctionne la classe ColorStorageFile dans le site des couleurs.
  3. Ajouter une méthode reinit() à AnimalStorageFile, qui remet la base dans l'état « initial », c'est-à-dire avec les animaux du TP précédent écrits en dur (Médor, Félix et Denver) (adapter à vos objets). Attention, contrairement au TP précédent, c'est ObjectFileDB qui contrôle les identifiants (qui sont au format hexadécimal).
  4. AnimalStorageFile doit implémenter l'interface AnimalStorage. Copier les méthodes read et readAll de la classe AnimalStorageStub, et les adapter si besoin.
  5. Modifier le routeur (ou le fichier de configuration) pour que le contrôleur utilise un AnimalStorageFile à la place de l'AnimalStorageStub. Attention, le serveur du département n'autorise pas PHP à écrire n'importe où. Le plus simple est de mettre le fichier de sauvegarde dans le répertoire /users/LOGIN/tmp. Bien sûr, sur un vrai site, on mettrait le fichier à un endroit plus sûr… Appeler reinit() sur la base après l'avoir créée.
  6. Tester : la liste doit fonctionner (mais suivre les liens génère une erreur, puisque reinit() est appelée entretemps et change tous les identifiants). Enlever l'appel à reinit() : tout doit marcher comme avant.
  7. Ajouter la méthode suivante à la vue :
    public function makeDebugPage($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.

Création d'un nouvel animal

  1. La page avec le formulaire de création d'un animal sera (par exemple) à l'URL animaux.php?action=nouveau, et la page destinée à recevoir les données sera à l'URL animaux.php?action=sauverNouveau (en remplaçant animaux.php par le nom de votre script). Ajouter au routeur des méthodes getAnimalCreationURL() et getAnimalSaveURL() (adapter les noms à vos objets) qui renvoient ces URLs.
  2. Ajouter une méthode makeAnimalCreationPage() (adapter le nom à vos objets) à la vue. Elle doit afficher un formulaire de création d'un animal, avec trois champs texte : nom, espece et age (et/ou d'autres, en fonction de la classe représentant vos objets). Le formulaire doit envoyer les données en POST à l'URL donnée par le getAnimalSaveURL() (ou votre équivalent) du routeur.
  3. Ajouter une méthode saveNewAnimal(array $data) au contrôleur, qui se contente d'afficher son argument grâce à makeDebugPage.
  4. Modifier le routeur pour qu'il analyse le paramètre action de l'URL, et qu'il appelle makeAnimalCreationPage() de la vue si le paramètre est nouveau, et qu'il appelle saveNewAnimal($_POST) du contrôleur si le paramètre est sauverNouveau. Cela permet de tester que tout fonctionne pour le moment.
  5. 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 avec makeAnimalPage. Ça devrait fonctionner, mais l'animal n'est pas ajouté à la base (on peut le constater en affichant la liste des animaux).
  6. Ajouter une méthode create(Animal $a) (adapter le type à vos objets)à l'interface AnimalStorage, et l'implémenter dans AnimalStorageFile. Cette méthode doit ajouter à la base l'animal donné en argument, et retourner l'identifiant de l'animal ainsi créé.
  7. Faire en sorte que saveNewAnimal ajoute le nouvel animal à la base avant de le passer à la vue.
  8. Vérifier que tout marche bien : on doit pouvoir créer des animaux qui s'ajoutent à la liste.

Validation

Les internautes peuvent maintenant ajouter leurs objets favoris à votre site. Cependant, ils 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.

  1. 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 (adapter si nécessaire aux attributs de vos objets). On pourra afficher une page d'erreur par exemple.
  2. 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 makeAnimalCreationPage de la vue, pour qu'elle remplisse les champs du formulaire avec. Faire les modifications nécessaires. (Attention notamment à ce que tous les appels à makeAnimalCreationPage soient corrects.)
  3. 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 à makeAnimalCreationPage qui se chargera de l'afficher. La chaîne sera null s'il n'y avait pas d'erreur, et contiendra sinon une explication sur l'erreur.
  4. Vérifier que tout fonctionne, c'est-à-dire le cas normal et tous les cas d'erreur.
  5. 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'objet). Que se passe-t-il ? Que faut-il faire pour empêcher ça ?

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 (adapter à vos objets), 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.

  1. 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.
  2. Modifier la méthode saveNewAnimal du contrôleur pour qu'elle crée une instance de AnimalBuilder avec le tableau $data qu'elle a elle-même reçu du routeur.
  3. Ajouter une méthode createAnimal() dans AnimalBuilder, qui crée une nouvelle instance de Animal en utilisant l'attribut $data (déplacer le code depuis le contrôleur).
  4. Ajouter une méthode isValid() dans AnimalBuilder, 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.
  5. Modifier la méthode makeAnimalCreationPage pour qu'elle ne prenne comme argument qu'une instance de AnimalBuilder, et l'utilise pour pré-remplir les champs et pour afficher l'erreur éventuelle.
  6. Modifier saveNewAnimal pour utiliser l'instance de AnimalBuilder. Vérifier que tout marche toujours (normalement non : il faut modifier l'appel initial à makeAnimalCreationPage, qui doit récupérer un AnimalBuilder vide ; qui doit s'occuper de le construire ?)
  7. Il ne reste plus qu'à « cacher » les noms des champs. Ajouter à AnimalBuilder des constantes NAME_REF, SPECIES_REF et AGE_REF (adapter aux champs des formulaires pour vos objets), qui contiennent respectivement nom, espece et age. Utiliser ces constantes dans la classe en question, et dans makeAnimalCreationPage à la place des noms de champ codés « en dur ».
  8. 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).

Suppression d'un animal (optionnel)

La suppression est beaucoup plus simple que la création (en particulier, pas besoin de AnimalBuilder). Les questions suivantes sont un peu moins guidées. Penser à tester vos ajouts au fur et à mesure, par exemple en utilisant l'affichage de debug !

  1. Ajouter au routeur des méthodes getAnimalAskDeletionURL($id) (page demandant à l'internaute de confirmer son souhait de supprimer l'animal) et getAnimalDeletionURL($id) (page supprimant effectivement l'animal). Faire en sorte qu'accéder à ces URL affiche quelque chose (en utilisant la méthode de debug, par exemple).
  2. Ajouter sur la page de chaque animal en lien vers la page de suppression. NB: il faut que makeAnimalPage ait accès à l'identifiant de l'animal dont elle affiche la page. On peut mettre l'identifiant comme attribut dans la classe Animal, mais ce n'est pas idéal (ce n'est pas une caractéristique de l'animal, mais un détail de l'implémentation du stockage…). Il est préférable de simplement passer l'identifiant en paramètre.
  3. Implémenter la méthode makeAnimalDeletionPage($id) de la vue, qui doit afficher un bouton de confirmation envoyant une requête POST à l'URL de suppression effective.
  4. Implémenter la méthode askAnimalDeletion($id) du contrôleur, qui vérifie que l'animal existe avant d'appeler makeAnimalDeletionPage($id) (un message d'erreur doit être affiché dans le cas contraire).
  5. Ajouter une méthode delete($id) à AnimalStorage et l'implémenter dans AnimalStorageFile.
  6. Implémenter la méthode deleteAnimal($id) du contrôleur, qui supprime effectivement l'animal.

Compléments (optionnel)

Assurez-vous de maîtriser ce qui précède avant de vous attaquer à ces questions moins guidées.

  1. Ajouter la possibilité de modifier un animal : les principes sont les mêmes que pour la création, mais il va falloir ajouter des méthodes à AnimalBuilder.
  2. Faire la section « Compléments » de l'exercice du TP précédent, et faire en sorte que les opérations CRUD fonctionnent toujours. Pour les URL plus propres, on pourra prendre par exemple animaux.php/nouveau, animaux.php/2328/supprimer, et animaux.php/2328/modifier, la distinction entre page de formulaire et page de « confirmation » pouvant se faire par le choix de la méthode HTTP utilisée (récupérable en PHP par $_SERVER['REQUEST_METHOD']).
  3. Au lieu d'afficher une seule chaîne avec les erreurs, faire en sorte que chaque erreur s'affiche à côté du champ concerné.