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

Alexandre Niveau
GREYC — Université de Caen
TODO:
  • le coup de la vue qui appelle une méthode POSTredirect du routeur c'est vraiment pas top. Réfléchir aux différents cas d'utilisation et faire un truc correct. A priori depuis l'extérieur de la vue on ne doit pas savoir ce qui est renvoyé (page ou redirection). Le PRG est spécifique au web.
  • c'est pas propre que le contrôleur enregistre les builders en session. il vaudrait mieux que le routeur enregistre les données POST qqpart jusqu'à ce que la validation passe ?

Rappels

Le fonctionnement de l'architecture est expliqué dans les cours précédents (architecture de base ; manipulation des données)

On repart de l'exemple des couleurs :
couleurs/
|-- skin/
|   `-- screen.css
|-- src/
|   |-- ctl/
|   |   `-- Controller.php
|   |-- lib/
|   |   `-- ObjectFileDB.php
|   |-- model/
|   |   |-- ColorBuilder.php
|   |   |-- Color.php
|   |   |-- ColorStorageFile.php
|   |   `-- ColorStorage.php
|   |-- view/
|   |   `-- MainView.php
|   `-- Router.php
`-- index.php

Le résultat de notre CRUD basique est visible ici, et le code est disponible dans cette archive.

Limites de notre approche basique

Notre site fonctionne, mais il n'est pas très robuste. On va voir à présent comment on peut améliorer son confort d'utilisation.

Considérer les exemples suivants :
  • Tout content d'avoir créé/modifié sa couleur, Jean-Michel la met dans ses marque-page ou envoie le lien à un ami. Que se passe-t-il ?
  • Dominique crée une couleur puis actualise régulièrement la page, pour vérifier que personne ne la modifie. Que se passe-t-il ?
  • Martine bidouille l'URL de la page de suppression définitive en modifiant l'identifiant : que se passe-t-il ? (Elle peut aussi générer un lien et l'envoyer à quelqu'un.)

Solution : POST-redirect-GET

POST-redirect-GET

Les pages destinées à être accédées en POST ne doivent pas être visibles directement par les internautes.

Il faut les rediriger immédiatement vers une page normale.

La logique est que les utilisateurs ont l'habitude de GET, qui est idempotente : la même requête donne toujours le même résultat.

En redirigeant systématiquement après un POST, on donne à chaque méthode son rôle de base tel que défini par HTTP : POST modifie les données, GET affiche les données.

La redirection doit utiliser le code de statut 303 See Other ; on peut utiliser une option de la fonction PHP header :
    header("Location: " $urltrue303);
démo: redirection après création et modif, si succès. TODO: c'est la vue qui doit s'occuper de la redirection

Feedback

Le POST-redirect-GET marche bien pour la création et la modification (sans erreur), car on redirige vers la page de la couleur

Pour la suppression, on voudrait pouvoir rediriger vers la galerie avec un feedback du type « La couleur a bien été supprimée »

Nécessite de se souvenir de ce qui s'est passé à la requête précédente : il faut utiliser les variables de session

Principe :
  • Une variable $_SESSION["feedback"] contient le feedback de la requête précédente.
  • Au début du traitement, le routeur récupère son contenu et la vide. Ce qui a été récupéré est passé à la vue.
  • Il peut ensuite y mettre toutes les informations utiles, qui seront affichées à la prochaine requête.
démo: ajout gestion feedback dans le routeur; utilisation dans la vue. modif fonction redirection pour prendre feedback. redirection vers galerie après suppression.

POST-redirect-GET, le retour

On a géré les cas sans erreur, mais pas les cas où le formulaire envoyé est invalide
  • l'internaute voit les URL « POST-only »
  • actualiser génère un message pour renvoyer les données ; le fonctionnement de l'historique est altéré (« document expiré »)
  • risque de perte de données pour l'internaute si appui sur entrée dans la barre d'adresse

On va donc appliquer le POST-redirect-GET jusqu'au bout : si données invalides, on redirige vers la page de formulaire initiale

il faut donc enregistrer en session les données reçues et les erreurs à afficher.

Il suffit de stocker l'instance de ColorBuilder dans une variable comme $_SESSION['currentColorBuilder']

Lors de l'accès en GET à une page de formulaire, le contrôleur donne à la vue le ColorBuilder qui est dans cette variable.

démo: ajout de cette variable de session et redirection après toute tentative de création.

Persistance des formulaires

On ne peut pas modifier une couleur existante si on a un ColorBuilder non validé en cours !

La meilleure solution est d'enregistrer en session plusieurs instances de ColorBuilder :
  • une pour les nouvelles couleurs
  • une pour chaque couleur existante en cours de modification

Pour le deuxième point, il suffit d'avoir une variable de session $_SESSION['modifiedColors'] qui contient un tableau d'instances de ColorBuilder, indexés par leur identifiant.

Grâce à cette modification, on a aussi amélioré l'utilisabilité de notre site, car les formulaires sont maintenant persistants : si un formulaire invalide a été soumis, il n'est pas perdu, même si on change de page.

En particulier, on peut éditer plusieurs couleurs en même temps sans risque que les différents formulaires s'écrasent entre eux.

Implémentation des améliorations

Le résultat de toutes nos améliorations est visible ici, et le code est disponible dans cette archive.

On présente maintenant encore d'autres améliorations, pas forcément complexes à implémenter mais un peu moins fondamentales. On ne les abordera pas toutes en TP, mais elles sont recommandées sur un véritable site.

Identifiants temporaires

  • On a pris soin de différencier en session les données et erreurs pour chaque couleur, pour que l'utilisateur puisse les modifier indépendamment
  • Il faudrait faire de même pour les nouvelles couleurs :
    • pour l'instant, si l'internaute abandonne la nouvelle couleur en cours, et qu'il/elle essaie d'en créer une nouvelle, il/elle retombe sur la précédente
    • Pas grave dans notre cas, mais pour un formulaire plus conséquent ça peut être très désagréable.
    • Solution : attribuer des identifiants temporaires aux couleurs en cours de création. Permet aussi de manipuler sans risque plusieurs formulaires de création en parallèle.

URL

On a utilisé des URL particulières pour les POST, mais on n'en a pas besoin : on peut réutiliser action=creerCouleur, action=modifier et action=supprimer, et vérifier si la page a été accédée avec GET ou avec POST (on peut le voir dans $_SERVER["REQUEST_METHOD"]

Au lieu d'utiliser des paramètres GET pour les actions et les identifiants, ce qui n'est pas propre, on peut exploiter la possibilité de mettre un chemin après le nom du script PHP dans l'URL, par ex index.php/couleurs/45. Ce chemin virtuel est récupérable grâce à la variable $_SERVER["PATH_INFO"] (attention, elle n'est pas présente s'il n'y a rien après le nom du script)

Dans tous les cas, les URL vues par les internautes peuvent être différentes des « vrais » chemins sur le serveur, grace à la réécriture d'URL. Avec Apache, on utilise le module mod_rewrite, utilisable dans les .htaccess. Principe : le client demande l'URL /couleurs/08/modifier, mais le serveur appelle en fait le script avec /couleurs/index.php?id=08&action=modifier ou /couleurs/index.php/08/modifier (en fonction de la technique utilisée dans votre architecture)

La syntaxe de mod_rewrite est notoirement abominable : moins vous l'utiliserez, mieux vous vous porterez.

Finitions

Implémenter un retour à zéro pour les formulaires de modification (les champs sont réinitialisés à la valeur présente dans la BD)

Sur les pages accédées en POST, on peut vérifier que l'internaute est bien passé·e par notre formulaire, par exemple en mettant une sorte de jeton en session ⇒ empêche les attaques de type cross-site request forgery (CSRF)

Frameworks

Des frameworks à comparer:
  • cake
  • symfony
  • laravel
  • slim framework
le dernier est très léger, sans doute d'un niveau proche de notre archi. à noter, simplification pour la déclaration des routes (mail de Arnaud Courdille du 16 mars 2017) :
Votre méthode :

case 'home':
    $this->view->makeHomePage($person);
break;

public function getHomeURL() {
    return $this->baseURL . "/home";
}


Slim Framework :

$app->get('/home', \App\controllers\ViewsController::class . ':home')->setName('home');

La méthode setName permettant de définir un nom qui sera utilisé dans les 
href et submit. Ainsi on peu changer l'URL sans changer les liens dans chaque 
fichiers.
\App\controllers\ViewsController::class   permet de d'indiquer le namespace du 
controller à utiliser et ':home' la méthode à utiliser.