Architecture d'un site web
Notes de cours
- Architecture d’un site web
- Séparation traitement des données & affichage
- Une architecture MVC pour un site web
Travail personnel
Exercice 1 — Introduction à l’architecture MVCR #
Cet exercice vise à construire un site web très simple utilisant l'architecture MVCR présentée en cours.
L'objectif est d'introduire pas à pas, en les justifiant, chacun des choix de conception présentés en cours. L'énoncé est donc très long, mais c'est parce que l'exercice est très guidé : il n'y a aucune difficulté particulière jusqu'à la section « Compléments ».
À la fin de l'exercice, vous devriez arriver à un résultat proche de l'exemple du site des couleurs présenté (en partie) en cours. Si nécessaire, vous pouvez regarder comment fonctionne le résultat final (version en ligne, archive du code), cependant cet énoncé étant très incrémental, vous passerez par des étapes que vous ne retrouverez pas forcément dans le résultat final du site des couleurs. N'utilisez surtout pas ce code pour faire du copier-coller, vous risquez de vous embrouiller dans les questions. C'est un outil de compréhension du cours, pas un corrigé.
Préliminaires
- Cloner votre dépôt Gitlab quelque part sur votre espace web. Si vous ne l'avez encore jamais cloné, suivez les instructions données sur la page du dépôt («Create a new repository»).
Dans toute la suite, on se place dans le répertoire en question — j'utiliserai le nom
exoMVCR
, mais pour vous ce sera quelque chose commegroupe-42
. - Modifier le fichier
README.md
pour y mettre les noms, numéros étudiants et logins des deux membres du binôme. - Créer dans
exoMVCR
un répertoiresrc
, qui contiendra tout le code source de l'application (c'est-à-dire tout ce qui n'est pas censé être directement accessible depuis un client). - Dans un fichier
src/Router.php
, créer une classeRouter
contenant uniquement une méthodemain
qui affiche quelque chose (par exemple « Hello world »). - Créer un fichier
site.php
à la racine deexoMVCR
avec le contenu suivant :<?php /* * On indique que les chemins des fichiers qu'on inclut * seront relatifs au répertoire src. */ set_include_path("./src"); /* Inclusion des classes utilisées dans ce fichier */ require_once("Router.php"); /* * Cette page est simplement le point d'arrivée de l'internaute * sur notre site. On se contente de créer un routeur * et de lancer son main. */ $router = new Router(); $router->main(); ?>
Normalement, en allant à l'URL du répertoire (par exemple
https://dev-NUMETU.users.info.unicaen.fr/exoMVCR/site.php
,
en adaptant le chemin), vous devriez voir le message du routeur.
Avant de passer à la suite, faites un commit du code courant avec comme message « Préliminaires ».
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.
Vue
- Créer un répertoire
view
danssrc
, contenant une classeView
avec deux attributs,$title
et$content
, et une méthoderender()
qui affiche une page HTML avec le contenu de ces attributs,$title
étant utilisé comme titre de la page, dans l'élémenthead
et comme unh1
dans lebody
, et$content
étant le contenu dubody
après leh1
. (La méthoderender()
peut se contenter d'inclure un squelette défini dans un autre fichier, mais ce n'est pas obligatoire.) - Ajouter une méthode
prepareTestPage()
àView
, qui remplit les attributs avec du texte quelconque. Attention : elle ne doit rien afficher, seulement remplir les attributs ! L'appel àrender
est le rôle du routeur : dans lemain
du routeur, créer une instance deView
et lui faire afficher la page de test. Tester le résultat, et vérifiez la validité de la page obtenue. - Ajouter une méthode
prepareAnimalPage($name, $species)
àView
, qui génère l'affichage d'une page sur l'animal passé en argument : par exemple, la page générée par l'appelprepareAnimalPage("Médor", "chien")
aura pour titre « Page sur Médor » et pour contenu « Médor est un animal de l'espèce chien ». Tester cet affichage depuis le routeur.
On a maintenant une page qui affiche des informations sur Médor. On va faire en sorte qu'elle puisse en afficher sur divers animaux, en fonction de l'URL. Pour cela, on va d'abord créer un contrôleur pour gérer les informations.
Avant de passer à la suite, faites un commit du code courant avec comme message « Début vue ». (voir les instructions ici (adapter le message de commit)).
Contrôleur
- Créer un répertoire
src/control
, contenant une classeController
avec un attribut$view
. Le constructeur doit prendre en argument une instance deView
qu'il met dans cet attribut. - Ajouter la méthode suivante dans le contrôleur :
public function showInformation($id) { $this->view->prepareAnimalPage("Médor", "chien"); }
Modifier le routeur pour qu'il fasse appel à cette méthode (avec un argument quelconque) plutôt que d'appeler lui-mêmeprepareAnimalPage
. Vérifier que tout fonctionne toujours comme avant, et corriger votre code si ce n'est pas le cas. - Ajouter un attribut
$animalsTab
au contrôleur, contenant le tableau suivant :array( 'medor' => array('Médor', 'chien'), 'felix' => array('Félix', 'chat'), 'denver' => array('Denver', 'dinosaure'), );
(et ce que vous voulez d'autre…). Utiliser ce tableau dansshowInformation
pour que le site affiche que Médor est un chien, Félix un chat, Denver un dinosaure, etc., en fonction de l'argument$id
passé à la méthode, mais affiche un message d'erreur (« Animal inconnu ») s'il ne connaît pas l'identifiant. Tester depuis le routeur en passant différents identifiants. - Si ce n'est déjà fait, faire en sorte que le message d'erreur mentionné à la question précédente soit généré par une méthode
prepareUnknownAnimalPage()
de la vue.
Avant de passer à la suite, faites un commit du code courant avec comme message « Début contrôleur ». (voir les instructions ici (adapter le message de commit)).
Routeur
- Modifier le routeur pour qu'il passe en argument à la méthode
showInformation
du contrôleur la valeur du paramètreid
de l'URL. - Tester : votre site devrait dire « Médor est un animal de l'espèce chien » si
on met le paramètre
id=medor
dans l'URL, « Denver est un animal de l'espèce dinosaure » pourid=denver
, etc., et devrait afficher le message d'erreur pour des identifiants inconnus. - Que se passe-t-il s'il n'y a pas de paramètre
id
dans l'URL ? Faire en sorte qu'une page d'accueil s'affiche, en modifiant le routeur, le contrôleur et la vue.
Avant de passer à la suite, faites un commit du code courant avec comme message « Début routeur ». (voir les instructions ici (adapter le message de commit)).
Modèle
On voudrait à présent donner plus d'information sur chaque animal :
outre son espèce, la page d'un animal doit maintenant donner son âge.
Cela reste possible d'ajouter un argument age
à la méthode prepareAnimalPage
de la vue,
mais on voit bien les limites de cette approche : que se passerait-il
si on avait des dizaines d'informations à donner sur chaque animal (taille, poids,
nourriture préférée, etc.) ?
La solution est de manipuler les animaux comme des instances
d'une classe Animal
.
- Dans un répertoire
src/model
, créer une classeAnimal
, qui a comme attributs (au minimum) un nom, une espèce et un âge. Créer le constructeur et les accesseurs appropriés (pas besoin de mutateurs, on ne modifiera pas les instances). - Modifier le tableau
$animalsTab
dans le contrôleur pour qu'il contienne des instances deAnimal
(attention : PHP ne voudra pas que vous définissiez le tableau en-dehors du constructeur dans ce cas), et changer la méthodeprepareAnimalPage
de la vue pour qu'elle ne prenne plus qu'unAnimal
en argument. Vérifier que tout fonctionne toujours. - Modifier la vue pour qu'elle affiche l'âge de l'animal.
Avant de passer à la suite, faites un commit du code courant avec comme message « Modèle ». (voir les instructions ici (adapter le message de commit)).
Liste des animaux
On voudrait à présent faire une page spéciale, d'URL site.php?action=liste
,
qui affiche la liste de tous les animaux dont le site connaît l'espèce.
- Ajouter une méthode
prepareListPage()
à la vue (qui pour l'instant affiche un contenu quelconque), une méthodeshowList()
au contrôleur qui appelleprepareListPage()
, et faire en sorte que le routeur appelleshowList()
lorsque l'URL contient un paramètreaction
de valeurliste
. Vérifier que ça fonctionne. - Faire en sorte que
prepareListPage
prenne en argument un tableau d'instances d'Animal
et affiche une liste des noms. - Modifier
showList
dans le contrôleur pour qu'elle passe àprepareListPage
la liste de tous les animaux. - Faire en sorte que chaque nom dans la liste soit un lien vers la page de l'animal en question.
- Pour faire la question précédente, avez-vous pris garde à ce que la vue
ne fasse pas d'hypothèses sur la façon dont sont construites les URL ?
Si ce n'est déjà fait, créer une méthode
getAnimalURL($id)
dans le routeur, qui renvoie l'URL de la page d'un animal. Attention, la vue doit donc avoir accès à une instance du routeur : l'ajouter comme attribut et comme argument au constructeur.
Avant de passer à la suite, faites un commit du code courant avec comme message « Liste d'animaux ». (voir les instructions ici (adapter le message de commit)).
Stockage
Pour l'instant, c'est le contrôleur qui « décide » des animaux connus par le site. Généralement, ce type d'information est plutôt contenu dans une base de données. On ne va pas utiliser de BD dans ce TP, mais on peut déjà adapter le contrôleur pour qu'il fonctionne dans tous les cas, BD ou non.
- Créer une interface
AnimalStorage
dans le répertoiresrc/model
; elle aura pour méthodesread($id)
, dont les implémentations doivent renvoyer l'instance deAnimal
ayant pour identifiant celui passé en argument, ounull
si aucun animal n'a cet identifiant ; etreadAll()
, dont les implémentations doivent renvoyer un tableau associatif identifiant ⇒ animal contenant tous les animaux de la « base ».
- Créer une classe
AnimalStorageStub
danssrc/model
qui implémente l'interfaceAnimalStorage
en utilisant des données écrites « en dur » (reprendre le tableau$animalsTab
du contrôleur). - Ajouter un argument de type
AnimalStorage
au constructeur du contrôleur. Le contrôleur doit enregistrer cette instance dans un attribut, et l'utiliser dans ses méthodesshowInformation
etshowList
à la place du tableau$animalsTab
. - Modifier le code du routeur pour qu'il passe une nouvelle instance de
AnimalStorageStub
au constructeur du contrôleur. -
À présent, le contrôleur est complètement indépendant de la façon dont sont stockées
les données. C'est le routeur qui décide d'utiliser un
AnimalStorageStub
, mais il aurait pu utiliser unAnimalStorageMySQL
, par exemple, sans que le contrôleur ne s'en rende compte. Cependant, ce n'est pas le rôle du routeur de choisir l'implémentation de l'interface de stockage !Faire en sorte que l'instance de
AnimalStorage
que le routeur utilise pour créer le contrôleur lui soit passée en paramètre de la méthodemain
. Ce sera doncsite.php
, c'est-à-dire le véritable point d'entrée de l'application, qui va prendre cette décision. (S'il prend plusieurs décisions de ce type, ce qui arrive très rapidement sur un site modérément compliqué, il peut être plus naturel de les déporter à nouveau dans un fichier de configuration, quesite.php
se contente d'inclure.)
Avant de passer à la suite, faites un commit du code courant avec comme message « Stockage ». (voir les instructions ici (adapter le message de commit)).
Compléments (optionnel)
- Comme on l'a vu en cours, les paramètres d'URL ne sont pas destinés
à faire du routage. Il est relativement simple
d'adapter le code pour que les URL soient de la forme
site.php/denver
, en utilisant la variable$_SERVER['PATH_INFO']
, qui contient la « partie virtuelle » de l'URL (le « chemin » qui se situe après le nom du script).Copiez votre routeur dans une nouvelle classe
PathInfoRouter
, modifiez le scriptsite.php
pour qu'il utilise une instance de cette classe comme routeur, puis adaptez la classe pour utiliser lePATH_INFO
au lieu des paramètres d'URL. Si votre code est propre, vous ne devriez avoir à changer que les méthodesmain
etgetAnimalURL
de votre nouveau routeur, et aucun autre fichier.
Avant de passer à la suite, faites un commit du code courant avec comme message « Compléments ». (voir les instructions ici (adapter le message de commit)).