TD1 – Paquets PHP Composer, Routage via l'URL
Dans les 3 premiers TDs, nous allons développer une API REST en PHP. Afin de pouvoir se concentrer sur l’apprentissage des nouvelles notions, nous allons partir du code existant d’un site Web de type réseau social appelé ‘The Feed’. Ce site contiendra un fil principal de publications et un système de connexion d’utilisateur.
L’intérêt de ce site est qu’il ne contient que 2 contrôleurs et un petit nombre d’actions :
- contrôleur
Publication:- lire les publications : action
afficherListe - écrire une publication : action
creerDepuisFormulaire
- lire les publications : action
- contrôleur
Utilisateur:- afficher la page personnelle avec seulement ses publications : action
afficherPublications - s’inscrire :
- formulaire (action
afficherFormulaireCreation), - traitement (action
creerDepuisFormulaire)
- formulaire (action
- se connecter :
- formulaire (action
afficherFormulaireConnexion), - traitement (action
connecter)
- formulaire (action
- se déconnecter : action
deconnecter
- afficher la page personnelle avec seulement ses publications : action
Mise en place du site de base
Ce TD nécessite de faire tourner un serveur Web PHP sous Docker. Si votre machine a gardé sa configuration du semestre 3, suivez les instructions de l’exercice 2 suivant (sautez l’exercice 1). Si vous devez installer Docker, suivez les instructions des exercices 1 et 2 suivants.
Installer Docker
Allez sur la page du dépôt Serveur Web Docker, et faites le tutoriel d’installation et de configuration qui se trouve dans la partie Tutoriel > Premier contact. Vous ne devez normalement pas y passer plus de 30 minutes.
Lancer votre serveur Web Docker
Démarrer Docker Desktop. Dans l’onglet Gauche Containers, relancez votre
conteneur à l’aide du bouton Play
.
Pour rappel, ce conteneur exécute un serveur Web PHP. Le port 80 du conteneur est relié par défaut au port 80 de la machine hôte. Le dossier /var/www/html du conteneur est relié par défaut au dossier public_html de la machine hôte.
En pratique, une fois le conteneur lancé, vous accédez aux pages Web situées
dans le dossier public_html de la machine hôte en naviguant à partir de l’URL
http://localhost via votre navigateur Web.
-
Dupliquez (fork) vous-même ce dépôt GitLab dans votre espace de nom (= votre login IUT). Cette opération est nécessaire pour que vous puissiez pousser vos modifications sur GitLab.
-
Clonez votre dépôt Git dupliqué dans un répertoire
public-html/ComplementWeb/TD1pour pouvoir y accéder via votre conteneur Docker. -
Dans votre conteneur, déplacez-vous à la racine de ce nouveau répertoire.
- Il faut donner les droits en lecture / exécution à Apache (utilisateur
www-data).setfacl -R -m u:www-data:r-x .Comme le site enregistre une photo de profil pour chaque utilisateur, il faut donner les droits en écriture sur le dossier
ressources/img/utilisateurs/.setfacl -R -m u:www-data:rwx ./ressources/img/utilisateursDans votre conteneur Docker, exécutez aussi les instructions suivantes :
chown -R root:www-data . chmod g+w ./ressources/img/utilisateurs/ - Importez les tables
utilisateursetpublicationsdans votre base de données SQL préférée :- Pour MySQL, vous devez :
- exécuter le script d’import MySQL,
- mettre à jour le fichier de configuration
src/Configuration/ConfigurationBDDMySQL.phpavec votre login et mot de passe.
- Pour PostgreSQL, vous devez :
- exécuter le script d’import PostgreSQL,
- mettre à jour le fichier de configuration
src/Configuration/ConfigurationBDDPostgreSQL.phpavec votre login et mot de passe, - préciser la bonne classe de configuration
ConfigurationBDDPostgreSQLau niveau du constructeur desrc/Modele/Repository/ConnexionBaseDeDonnees.php - dans les classes
PublicationRepositoryetUtilisateurRepository, modifier les$data['nomDeColonne']pour mettre tous les noms de colonnes en minuscule. En effet, PostgreSQL passe en minuscule tous les identifiants (sauf s’ils sont entourés de guillemets doubles", auquel car il faudra toujours y faire référence avec des guillemets doubles).
- Pour MySQL, vous devez :
-
Pour plus de confort, vous pouvez éventuellement augmenter la durée de session d’un utilisateur en augmentant la valeur retournée dans la méthode
getDureeExpirationSessionde la classesrc/Configuration/Configuration.php(actuellement réglée sur 120 secondes). -
Créez un nouvel utilisateur et une nouvelle publication.
Souvenez-vous bien de votre identifiant et mot de passe car nous nous en resservirons. - Faites marcher le site. Explorez toutes les pages.
Dans l’optique de développer une API REST, nous aurons besoin que les URL des pages de notre site n’utilisent plus le query string.
Par exemple, la route
web/controleurFrontal.php?controleur=publication&action=afficherListe
va devenir web/publications. Et la route
web/controleurFrontal.php?controleur=utilisateur&action=afficherFormulaireConnexion
deviendra web/connexion.
Pour ceci, nous allons utiliser une bibliothèque PHP existante, et donc un
gestionnaire de bibliothèques : Composer.
Le gestionnaire de paquets Composer
Composer est utilisé dans le cadre du développement d’applications PHP pour
installer des composants tiers. Composer gère un fichier appelé
composer.json qui référence toutes les dépendances de votre application.
Initialisation et Autoloading de Composer
Composer fournit un autoloader, c.-à-d. un chargeur automatique de classe,
qui satisfait la spécification PSR-4. En effet, cet autoloader est très
pratique pour utiliser les paquets que nous allons installer via Composer.
Commençons donc par remplacer notre autoloader Psr4AutoloaderClass.php par celui de Composer.
-
Créer un fichier
composer.jsonà la racine du site Web avec le contenu suivant{ "autoload": { "psr-4": { "TheFeed\\": "src" } } } - Si vous modifiez le fichier
composer.json, par exemple pour mettre à jour vos dépendances, vous devez exécuter la commande :composer updateAide : Pour ceux qui sont sur leur machine personnelle, vous devrez installer
composersur votre machine. Aller voir la documentation decomposerà cet effet. Pour Linux, il suffit d’installer un paquet. Pour Windows avec XAMPP, l’installateur Windows marche très bien. -
Quand on installe une application ou un nouveau composant,
composerplace les librairies téléchargées dans un dossiervendor. Il n’est pas nécessaire de versionner ce dossier souvent volumineux.
Rajoutez donc une ligne/vendor/à votre.gitignore. Dites aussi à Git d’ignorer son fichier de configuration interne/composer.lock. -
Modifiez le fichier
web/controleurFrontal.phpcomme suit :-use TheFeed\Lib\Psr4AutoloaderClass; - -require_once __DIR__ . '/../src/Lib/Psr4AutoloaderClass.php'; - -// initialisation en désactivant l'affichage de débogage -$chargeurDeClasse = new Psr4AutoloaderClass(false); -$chargeurDeClasse->register(); -// enregistrement d'une association "espace de nom" → "dossier" -$chargeurDeClasse->addNamespace('TheFeed', __DIR__ . '/../src'); +require_once __DIR__ . '/../vendor/autoload.php';Aide : Ce format montre une modification de fichier, similaire à la sortie de
git diff. Les lignes qui commencent par des+sont à ajouter, et les lignes avec des-à supprimer. - Testez votre site qui doit marcher normalement.
Archivage du routeur par query string
Nous allons déplacer le code de routage actuel dans une classe séparée, dans le but de bientôt le remplacer.
-
Dans le fichier
web/controleurFrontal.php, faites le changement suivant. Toutes les lignes supprimées de ce fichier doivent être déplacées dans la méthode statiquetraiterRequeted’une nouvelle classesrc/Controleur/RouteurQueryString.php.-// Syntaxe alternative -// The null coalescing operator returns its first operand if it exists and is not null -$action = $_REQUEST['action'] ?? 'afficherListe'; - - -$controleur = "publication"; -if (isset($_REQUEST['controleur'])) - $controleur = $_REQUEST['controleur']; - -$controleurClassName = 'TheFeed\Controleur\Controleur' . ucfirst($controleur); - -if (class_exists($controleurClassName)) { - if (in_array($action, get_class_methods($controleurClassName))) { - $controleurClassName::$action(); - } else { - $controleurClassName::afficherErreur("Erreur d'action"); - } -} else { - \TheFeed\Controleur\ControleurGenerique::afficherErreur("Erreur de contrôleur"); -} +\TheFeed\Controleur\RouteurQueryString::traiterRequete(); -
Testez votre site qui doit marcher normalement.
Nouveau routeur par Url
-
Créez une nouvelle classe
src/Controleur/RouteurURL.phpvide avec le code suivant.<?php namespace TheFeed\Controleur; class RouteurURL { public static function traiterRequete() { } } -
Appelez ce nouveau routeur en modifiant
web/controleurFrontal.php:-TheFeed\Controleur\RouteurQueryString::traiterRequete(); +TheFeed\Controleur\RouteurURL::traiterRequete();
Nous allons maintenant coder ce nouveau routeur.
Le composant HttpFoundation
Comme le dit sa
documentation,
le composant HttpFoundation défini une couche orientée objet pour la
spécification HTTP. En PHP, une requête est représentée par des variables
globales ($_GET, $_POST, $_FILES, $_COOKIE, $_SESSION, …), et la
réponse est générée par des fonctions (echo, header(), setcookie(), …).
Le composant HttpFoundation de Symfony remplace ces variables globales et
fonctions par une couche orientée objet.
Pour information, Symfony est l’un des 2 principaux framework de
développement de site Web professionnels en PHP. Dans ce cours, nous nous
attacherons aux notions derrière Symfony plutôt qu’à Symfony lui-même.
Ainsi, vos connaissances vous permettront de vous adapter plus facilement à de
nouveaux outils, que ce soit Symfony ou autre chose… Pour ces raisons, nous
n’utiliserons que des composants de Symfony.
Dans notre cas, nous allons tout d’abord utiliser la classe Request de
HttpFoundation pour représenter une requête HTTP. Notez que HttpFoundation
possède des classes aussi pour les réponses HTTP, les en-têtes HTTP, les
cookies, les sessions (et les messages flash 😉). Les classes liées aux réponses
HTTP seront abordées dans le TD2.
-
Exécutez la commande suivante dans le terminal ouvert au niveau de la racine de votre site web
composer require symfony/http-foundationRemarque : Certaines dépendances de Symfony nécessite une version de PHP
> 8.1. Si vous n’avez pas cette version sur votre machine personnelle, vous pouvez peut-être demander une version plus ancienne de cette dépendance.
Dans un premier temps, notre site va utiliser des URL comme
web/controleurFrontal.php/publications
web/controleurFrontal.php/connexion
web/controleurFrontal.php/utilisateurs/2/publications
La classe Request sera intéressante notamment car elle permet de récupérer la
partie du chemin qui nous intéresse : /publications, /connexion ou /utilisateurs/2/publications.
- Dans
RouteurURL::traiterRequete(), initialisez l’instance suivante de la classeRequeteuse Symfony\Component\HttpFoundation\Request; $requete = Request::createFromGlobals();Explication : La méthode
createFromGlobals()récupère les informations de la requête depuis les variables globales$_GET,$_POST, … Elle est à peu près équivalente à$requete = new Request($_GET,$_POST,[],$_COOKIE,$_FILES,$_SERVER); -
La méthode
$requete->getPathInfo()permet d’accéder au bout d’URL qui nous intéresse (/publications,/connexionou/inscription).Affichez cette variable dans
RouteurURL::traiterRequete()et accédez aux URL précédentes pour voir le chemin s’afficher.
Le composant Routing
Comme l’indique sa
documentation, le composant
Routing de Symfony va permettre de faire l’association entre une URL (par
ex. /publications ou /connexion) et une action, c’est-à-dire une fonction PHP comme
ControleurPublication::afficherListe.
- Exécutez la commande suivante dans le terminal ouvert au niveau de la racine
de votre site web
composer require symfony/routing -
Créez votre première route avec le code suivant à insérer dans
RouteurURL::traiterRequete():use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; $routes = new RouteCollection(); // Route afficherListe $route = new Route( path: "/publications", defaults: [ "_controller" => "\TheFeed\Controleur\ControleurPublication::afficherListe", ] ); $routes->add("afficherListe", $route);Explication : Une nouvelle
Route $routeassocie au chemin (paramètrepath)/publicationsla méthodeafficherListe()deControleurPublication(paramètredefaults). Puis cette route est ajoutée dans l’ensemble de toutes les routesRouteCollection $routes.On peut éventuellement simplifier en créant directement une instance de
Routedans l’appel à$routes->add:$routes->add("afficherListe", new Route( path: "/publications", defaults: [ "_controller" => "\TheFeed\Controleur\ControleurPublication::afficherListe", ] )); - Les informations de la requête essentielles pour le routage (méthode
GETouPOST, query string, paramètres POST, …) sont extraites dans un objet séparé :use Symfony\Component\Routing\RequestContext; $contexteRequete = (new RequestContext())->fromRequest($requete);Ajoutez cette ligne et affichez temporairement son contenu.
- Nous pouvons alors rechercher quelle route correspond au chemin de la requête
courante :
use Symfony\Component\Routing\Matcher\UrlMatcher; $associateurUrl = new UrlMatcher($routes, $contexteRequete); $donneesRoute = $associateurUrl->match($requete->getPathInfo());Ajoutez ce code et affichez temporairement le contenu de
$donneesRoute. Où se trouve l’information de la méthode PHP à appeler ? - Ajoutez le code suivant pour appeler enfin l’action PHP correspondante :
$donneesRoute["_controller"]();Explication : Ce code exécute la fonction dont le nom est stocké dans
$donneesRoute["_controller"]. - Votre site doit désormais répondre correctement à une requête à l’URL
web/controleurFrontal.php/publications, sauf les liens vers le CSS et les photos qui deviennent invalides.
Réécriture d’URL
Passons à notre deuxième route : /connexion.
-
Ajoutez la deuxième route :
use TheFeed\Controleur\ControleurUtilisateur; // Route afficherFormulaireConnexion $route = new Route( path: "/connexion", defaults: [ "_controller" => "\TheFeed\Controleur\ControleurUtilisateur::afficherFormulaireConnexion", // Syntaxes équivalentes // "_controller" => ControleurUtilisateur::class . "::afficherFormulaireConnexion", // "_controller" => [ControleurUtilisateur::class, "afficherFormulaireConnexion"], ] ); $routes->add("afficherFormulaireConnexion", $route);Notez les syntaxes équivalentes :
- l’attribut statique constant
NomDeClasse::classd’une classeNomDeClasseest remplacé par le nom de classe qualifié, c.-à-d. le nom de classe précédé du nom de package. Ici,ControleurUtilisateur::classa pour valeur la chaîne de caractères\TheFeed\Controleur\ControleurUtilisateur. - De manière générale, la valeur associée à
_controllerdevra être au formatcallable, car c’est ce qui est accepté lorsque l’on fait$donneesRoute["_controller"](). Parmi lescallable, on trouve le format"NomDeClasseQualifie::nomMethodeStatiqueou["NomDeClasseQualifie", "nomMethodeStatique"]pour les méthodes statiques, ou encore[$instanceDeLaClasse, "nomMethode"]ou$instanceDeLaClasse->nomMethode(...)pour les méthodes classiques.
- l’attribut statique constant
-
Testez la page
web/controleurFrontal.php/connexionqui doit marcher, sauf les liens vers le CSS et les photos qui deviennent invalides. Cherchez pourquoi ces liens se sont cassés.Aide : Dans le code source de la page Web (
Ctrl+U), cliquez sur ces liens cassés pour voir sur quel URL ils renvoient.
Nous allons régler ce problème en changeant l’URL de nos pages de
web/controleurFrontal.php/connexion vers une URL plus classique
web/connexion. Pour ceci, nous allons configurer Apache pour rediriger la
requête web/connexion vers l’URL web/controleurFrontal.php/connexion.
-
Enregistrez ce fichier de configuration d’Apache fourni par Symfony à la place de
web/.htaccess.Remarque :
- Si la réécriture d’URL ne marche pas à l’IUT (message d’erreur
Internal Server Error), vous avez peut-être enregistré le fichier dans.htaccessau lieu deweb/.htaccess. - Si la réécriture d’URL sur votre machine personnelle ne marche
pas, une cause possible est qu’il faut activer le module
mod_rewritede votre serveur Apache.
- Si la réécriture d’URL ne marche pas à l’IUT (message d’erreur
-
Testez que la page
web/connexionmarche et que le CSS et les images sont revenus. En effet, l’URL de base des liens relatifs est de nouveauweb/. -
Changez les liens dans
vueGenerale.php:-<a href="controleurFrontal.php?controleur=publication&action=afficherListe"><span>The Feed</span></a> +<a href="./publications"><span>The Feed</span></a> -<a href="controleurFrontal.php?controleur=publication&action=afficherListe">Accueil</a> +<a href="./publications">Accueil</a> -<a href="controleurFrontal.php?action=afficherFormulaireConnexion&controleur=utilisateur">Connexion</a> +<a href="./connexion">Connexion</a>
Route selon la méthode HTTP
L’un des avantages de notre routage est qu’il peut rediriger différemment selon la méthode HTTP employée. Voici ce que nous allons faire :
- URL
/connexion, méthodeGET→ actionafficherFormulaireConnexiondu contrôleur utilisateur - URL
/connexion, méthodePOST→ actionconnecterdu contrôleur utilisateur
Pour limiter une route à certaines méthodes HTTP, on peut définir un paramètre methods lors de
la création de l’instance de Route :
$route = new Route(
path: "/chemin",
defaults: [
"_controller" => "...",
],
methods: [Request::METHOD_GET]
);
Comme vous pouvez le constater, comme methods est un tableau, il est éventuellement possible d’autoriser
plusieurs méthodes pour une même route.
Request::METHOD_GET est une constante (qui donne la chaîne de caractères GET). Il est bien sûr aussi
possible de spécifier directement le nom de la méthode sans passer par la constante, mais cela permet d’éviter
des éventuelles erreurs de saisie. Il existe des constantes pour chaque méthode HTTP.
-
Modifiez votre routeur pour avoir les 2 routes
web/connexionselon la méthode HTTP.Attention : Le nom de chaque route doit être unique (
$routes->add("nomRoute", $route);). Si vous définissez deux routes avec le même nom, la deuxième écrase la première. -
Corrigez l’URL vers laquelle renvoie
src/vue/utilisateur/formulaireConnexion.php. -
Essayez de vous connecter au site : vous devez avoir une erreur
Uncaught Symfony\...\NoConfigurationException. En effet, si la connexion réussit, alors elle redirige vers l’ancienne adresseweb/?action=afficherListe&controleur=publication. Comme cette adresse est désormais inconnue, Symfony nous renvoieNoConfigurationException. -
Pour régler temporairement le problème des redirections (qui sera traité proprement à la fin du TD), rajoutons une route pour l’URL
web/. Dupliquez la routeafficherListeen changeant le path (/publications→/) et en donnant une autre nom à la route pour ne pas écraser la précédente. -
Essayez de vous connecter au site. Cela doit marche normalement.
Ajout des routes manquantes
- Ajoutez les routes manquantes (sauf celle vers
afficherPublications) :- URL
/deconnexion, méthodeGET→ actiondeconnecterdu contrôleur utilisateur - URL
/inscription, méthodeGET→ actionafficherFormulaireCreationdu contrôleur utilisateur - URL
/inscription, méthodePOST→ actioncreerDepuisFormulairedu contrôleur utilisateur - URL
/publications, méthodePOST→ actioncreerDepuisFormulairedu contrôleur publication.
Attention : Il y a déjà une autre route associée à l’URL/publications(actionafficherListe). Il faudra donc spécifier que l’autre route est limité à la méthodeGET. Pensez-bien à donner des noms uniques à vos routes!
- URL
- Modifiez les liens correspondants dans
src/vue/publication/liste.php,src/vue/utilisateur/formulaireCreation.phpsrc/vue/vueGenerale.php.
Vous ne pouvez pas encore mettre à jour les liens vers
controleurFrontal.php?controleur=utilisateur&action=afficherPublications car
nous n’avons pas encore créé la route correspondante. C’est ce que nous allons
faire dans la prochaine section.
Routes variables
Avec l’ancien routeur RouteurQueryString, nous pouvions envoyer des
informations supplémentaires dans l’URL, par exemple l’identifiant d’un
utilisateur avec controleur=utilisateur&action=afficherPublications&idUtilisateur=19.
Dans notre nouveau système d’URL, certaines parties de l’URL serviront à
récupérer ces informations supplémentaires. Par exemple, nous allons configurer
notre site pour que l’URL web/utilisateurs/19/publications renvoie vers la liste des publications
de l’utilisateur d’identifiant 19. Le routeur fourni par Symfony permet des
routes variables /utilisateurs/{idUtilisateur}/publications qui permettront d’extraire $idUtilisateur de
l’URL.
- Créez une nouvelle route :
- URL
/utilisateurs/{idUtilisateur}/publications, méthodeGET→ actionafficherPublicationsdu contrôleur utilisateur
- URL
-
Modifiez
afficherPublications()(de la classeControleurUtilisateur) pour qu’il prenne$idUtilisateuren argument au lieu de le lire depuis le query string avec$_REQUEST['idUtilisateur'].-public static function afficherPublications(): void +public static function afficherPublications($idUtilisateur): void - if (isset($_REQUEST['idUtilisateur'])) { - $idUtilisateur = $_REQUEST['idUtilisateur']; - } else { - MessageFlash::ajouter("error", "Login manquant."); - ControleurUtilisateur::rediriger("publication", "afficherListe"); - }Attention à ne pas supprimer le reste ! La fonction doit toujours faire les autres instructions (récupérer l’utilisateur via le repository, charger la vue, etc.). On supprime seulement la partie qui récupère l’identifiant de l’utilisateur dans
$_REQUESTen introduisant un paramètre. -
Si vous testez la route, vous verrez qu’elle ne marche pas, car
$donneesRoute["_controller"]()appelleafficherPublicationssans lui donner d’arguments (il attend$idUtilisateur). - Affichez
$donneesRoutepour voir commentUrlMatchera extraitidUtilisateurde l’URL.
Nous allons résoudre ce problème en introduisant un nouveau composant.
Le composant HttpKernel de Symfony
Selon sa
documentation, le
composant HttpKernel de Symfony fournit un processus structuré pour
convertir une Request en Response. Sa classe principale HttpKernel est
similaire à notre RouteurURL, mais en plus évolué. Nous ne nous servirons donc
pas de HttpKernel puisque nous recodons une version simplifiée plus
compréhensible.
Nous allons plutôt nous concentrer sur les classes ControllerResolver et
ArgumentResolver. La responsabilité du résolveur de contrôleur est de
déterminer le contrôleur et la méthode à appeler en fonction de la requête. La
classe ControllerResolver se limite plus ou moins à lire
$donneesRoute["_controller"]. Nous pourrions nous en passer, mais elle sera
utile plus tard quand vous aurez des actions qui sont des méthodes non statiques
(cf. séance sur les tests avec PhpUnit).
La classe ArgumentResolver va construire la liste des arguments de l’action du
contrôleur. Par exemple, c’est cette classe qui va créer l’argument $idUtilisateur
avec la valeur 19 pour la méthode ControleurUtilisateur::afficherPublications($idUtilisateur).
-
Importez le composant
HttpKernelcomposer require symfony/http-kernel -
Faites évoluer le code de
RouteurURLen rajoutant à la fin (juste avant$donneesRoute["_controller"]())use Symfony\Component\HttpKernel\Controller\ArgumentResolver; use Symfony\Component\HttpKernel\Controller\ControllerResolver; $requete->attributes->add($donneesRoute); $resolveurDeControleur = new ControllerResolver(); $controleur = $resolveurDeControleur->getController($requete); $resolveurDArguments = new ArgumentResolver(); $arguments = $resolveurDArguments->getArguments($requete, $controleur);et en modifiant
-$donneesRoute["_controller"](); +$controleur(...$arguments); -
Testez la route
web/utilisateurs/19/publicationsen remplaçant19par un identifiant d’utilisateur ayant quelques publications. La page doit remarcher, mais pas le CSS ni les images.
Plus d’explications (optionnel) :
Revenons sur la classe ArgumentResolver pour expliquer son fonctionnement
(simplifié)
sur l’exemple afficherPublications() :
- En utilisant l’introspection de PHP, le code accède à la liste des arguments (type et nom)
- pour chaque argument, on essaye itérativement l’un des résolveurs
d’arguments
pour déterminer la valeur de l’argument.
Dans notre exemple, le premier résolveur (classeRequestAttributeValueResolver) va regarder si le nom de l’argumentidUtilisateurest présent dans$requete->attributes(équivalent à$donneesRoute). Comme c’est le cas alors on renvoie cette valeur.
L’avantage de ce mécanisme est qu’il permet de récupérer beaucoup de types d’arguments dans le contrôleur :
- un attribut extrait de la requête (attribut
GETouPOST). Pour ceci, le nom de l’attribut doit correspondre, - la requête
Request $requete(l’argument doit avoir le typeRequest), - la valeur par défaut d’une route variable,
- des services du conteneur de service (cf. future séance SAÉ sur les tests
avec
PHPUnit), - des éléments de la base de données si le type correspond à celui d’une entité
(
DataObjectdans ce cours)
Générateur d’URL et conteneur global
Les liens vers le style CSS et les images de profil de notre site sont souvent cassés car elles utilisent des URL relatives. En effet, la base de l’URL varie selon le chemin demandé :
- pour le chemin
web/connexion, les URL relatives utilisent la baseweb/. - pour le chemin
web/utilisateurs/19/publications, les URL relatives utilisent la baseweb/utilisateurs/19. Du coup, les liens relatifs sont cassés.
Nous allons utiliser des classes de Symfony pour générer automatiquement des
URL absolues. D’un côté, nous allons utiliser UrlHelper pour générer des URL
absolues à partir d’URL relatives :
use Symfony\Component\HttpFoundation\RequestStack;
$assistantUrl = new UrlHelper(new RequestStack(), $contexteRequete);
$assistantUrl->getAbsoluteUrl("../ressources/css/styles.css");
// Renvoie l'URL .../ressources/css/styles.css, peu importe l'URL courante
D’un autre côté, la classe UrlGenerator génère des URL absolues à partir du
nom d’une route. C’est pratique si on doit changer le chemin de la route a
posteriori.
use Symfony\Component\Routing\Generator\UrlGenerator;
$generateurUrl = new UrlGenerator($routes, $contexteRequete);
$generateurUrl->generate("creerDepuisFormulaire");
// Renvoie ".../web/publications"
$generateurUrl->generate("afficherPublications", ["idUtilisateur" => 19]);
// Renvoie ".../web/utilisateurs/19/publications"
Comme nous allons avoir besoin de ces services de génération d’URL dans différentes vues, il faut pouvoir les initialiser au début de l’application, et pouvoir y accéder globalement. Dans le cours de développement Web du semestre 3, nous avions fait le choix d’avoir des classes statiques utilisant le patron de conception Singleton. Ce choix a l’inconvénient de rendre difficile les tests.
En attendant la séance de SAÉ sur les tests avec PhpUnit, nous allons utiliser
une classe Conteneur pour stocker globalement les services dont nous aurons
besoin.
-
Créez une classe
src/Lib/Conteneur.phpavec le code suivant :<?php namespace TheFeed\Lib; class Conteneur { private static array $listeServices; public static function ajouterService(string $nom, $service) : void { Conteneur::$listeServices[$nom] = $service; } public static function recupererService(string $nom) { return Conteneur::$listeServices[$nom]; } } -
Initialisez les deux services
$assistantUrlet$generateurUrldansRouteurUrl(avant l’appel à$donneesRoute["_controller"](...arguments)). Puis stockez-les dans le conteneur.$generateurUrl = new UrlGenerator($routes, $contexteRequete); $assistantUrl = new UrlHelper(new RequestStack(), $contexteRequete); Conteneur::ajouterService("generateurUrl", $generateurUrl); Conteneur::ajouterService("assistantUrl", $assistantUrl); -
Récupérez les deux services en haut de la vue
vueGenerale.php.use Symfony\Component\HttpFoundation\UrlHelper; use Symfony\Component\Routing\Generator\UrlGenerator; use TheFeed\Lib\Conteneur; /** @var UrlGenerator $generateurUrl */ $generateurUrl = Conteneur::recupererService("generateurUrl"); /** @var UrlHelper $assistantUrl */ $assistantUrl = Conteneur::recupererService("assistantUrl");Puis utilisez-les dans toutes les vues pour passer tous les liens en URL absolues, soit à partir du nom d’une route, soit à partir du chemin relatif d’un
asset. À la fin, vous devez avoir corrigé tous les liens :<a href="">,<img src="">,<form action="">et<link href="">.Remarques :
$generateurUrl->generate()échappe les caractères spéciaux des URL. Vous devez donc lui donner les données brutes, et non celles échappées parrawurlencode().$assistantUrl->getAbsoluteUrl()n’échappe pas les caractères spéciaux des URL. À vous de le faire.- Vous pouvez utiliser la syntaxe raccourcie
<?= $var ?>équivalente à<?php echo $var ?>pour améliorer la lisibilité de vos vues.
Il ne nous reste qu’à mettre à jour la méthode de redirection et notre site aura fini sa première migration pour des routes basées sur les URL !
-
Changer la méthode
ControleurGenerique::rediriger()pour qu’elle prenne en entrée le nom d’une route et un tableau optionnel de paramètres pour les routes variables (mêmes arguments que$generateurUrl->generate()). Cette fonction doit maintenant rediriger vers l’URL absolue correspondante. Vous aurez besoin de récupérer un service duConteneur. -
Mettez-à-jour tous les appels à
ControleurGenerique::rediriger(). -
Testez votre site.
Conclusion
Dans ce TD, nous avons découvert comment changer les URL associées à notre site pour qu’elles soient plus standard. Cela a été l’occasion de plonger dans le fonctionnement interne d’un routeur professionnel. Ceci vous sera utile si vous apprenez Symfony ou un autre framework backend plus tard. Concernant le cours Complément Web, le passage à ces URL est une étape nécessaire dans notre chemin pour développer une API REST.
Enfin, maintenant que vous connaissez les bases de Composer, vous pouvez facilement rajouter des bibliothèques à votre site web PHP.
Si vous souhaitez améliorer le système de création des routes pour utiliser des
attributs au-dessus de chaque action liée à une route (dans les contrôleurs) au
lieu de tout définir dans traiterRequete, vous pouvez lire cette
note complémentaire.