Fil d'Ariane
Drupal 10 : comment fonctionne le routage
Découvrir la façon dont Drupal traite les requêtes entrantes et détermine la réponse à y apporter.
Cet article a été initialement rédigé pour Drupal 8 mais son contenu est toujours d'actualité pour Drupal 9 et Drupal 10.
N'hésitez pas à nous contacter ou à vous inscrire à notre formation «Drupal pour les développeurs et développeuses» pour en savoir plus !
Depuis quelques années, les applications se sont complexifiées. Les APIs se sont multipliées et avec leur usage le respect de la norme HTTP s’est amélioré.
Pourquoi parler de HTTP ? Car c’est le fondement de toute action que vous faites avec un navigateur.
Lorsque vous cliquez sur un lien, lorsque vous soumettez un formulaire, un verbe HTTP est utilisé. Vous les connaissez, il s’agit respectivement des verbes GET et POST mais il en existe d’autres (PUT, DELETE, etc.). Depuis Drupal 8, en s’appuyant sur le composant Symfony HTTPFoundation, on respecte bien mieux ces verbes que dans ses versions précédentes.
A chaque fois que vous faites appel à une ressource (au sens URL ou URI), deux actions se déroulent en permanence : l’appel de la ressource, la requête (Request) et la construction de sa réponse (Response). La réponse d’une requête est générée par un contrôleur.
Les requêtes et les réponses peuvent donc être manipulées à travers deux objets distincts grâce au composant Symfony HTTPFoundation. Et le Routage dans tout cela ?
Le Routage sert à identifier quel contrôleur doit être utilisé pour générer la réponse à retourner à la requête.
Pour cela, Drupal s’appuie maintenant sur le système de routes de Symfony.
Une Route représente une ressource à laquelle nous faisons appel pour servir une page. C’est ce qui fait la connexion entre une URI et le code qui va générer sa réponse. Voici un exemple de route :
#user.routing.yml
user.logout:
path: '/user/logout'
defaults:
_controller: '\Drupal\user\Controller\UserController::logout'
requirements:
_user_is_logged_in: 'TRUE'
Chaque route se voit attribuée un nom arbitraire qui permettra de la désigner à travers notre base de code (pour faire un lien par exemple). La définition d’une route agrège à minima les paramètres suivants :
- path (obligatoire) : chemin par lequel l’utilisateur accède à la route
- defaults (obligatoire) : tableau indiquant quelles données utiliser lorsque la route est appelée
- requirements (obligatoire) : tableau de conditions à valider pour pouvoir accéder à la route.
Pour connaître les subtilités des données à envoyer aux routes, reportez-vous à la page de documentation dédiée. (https://www.drupal.org/node/2092643)
Un exemple de réponse de route est le suivant :
# UserController.php
namespace Drupal\user\Controller;
use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
/**
* Controller routines for user routes.
*/
class UserController extends ControllerBase {
/**
* Logs the current user out.
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse
* A redirection to home page.
*/
public function logout() {
if ($this->currentUser()->isAuthenticated()) {
user_logout();
}
return $this->redirect('<front>');
}
}
Le principal avantage apporté par les routes réside dans le fait que si vous voulez supplanter le comportement par défaut d’une route (comme modifier la permission nécessaire pour accéder à la page) il vous suffit d’écrire quelques lignes de code pour indiquer au système de générer une réponse totalement différente.
# RouteSubscriber.php
use Drupal\Core\Routing\RouteSubscriberBase;
use Symfony\Component\Routing\RouteCollection;
/**
* Listens to the dynamic route events.
*/
class RouteSubscriber extends RouteSubscriberBase {
/**
* {@inheritdoc}
*/
public function alterRoutes(RouteCollection $collection) {
// Change path '/user/login' to '/login'.
if ($route = $collection->get('user.login')) {
$route->setPath('/login');
}
}
}
# module.services.yml
services:
foo.route_subscriber:
class: Drupal\foo\Routing\RouteSubscriber
tags:
- { name: event_subscriber }
Ce qui est très intéressant et très puissant c’est qu’en modifiant simplement un en-tête lors de l’appel d’une route, la réponse d’une page peut être radicalement différente. Elle peut par exemple retourner du JSON ou du HTML et ce simplement grâce à l’ajout d’un paramètre _format
.
L’intérêt est de gérer ce genre de demande très tôt dans la génération de la page (et donc à un niveau très bas) pour éviter les calculs et traitements inutiles. Pourquoi attacher des feuilles de styles et fichiers Javascript si vous allez faire une réponse en JSON ?
Compléments d'information
Le hook_menu()
de Drupal a disparu, les différentes tâches dont il était responsable sont remplacées par la déclaration des fichiers :
- <module_name>.routing.yml permettant la définition de routes
- <module_name>.links.<type>.yml pour générer des entrées de menu (liens classiques “menu”, onglets locaux “tasks”, liens contextuels “action”, etc).
Il ne faut pas oublier de définir l’espace de nom (Namespace), il doit correspondre à Drupal\<module_name>\Controller
et de respecter PSR-4 pour les contrôleurs en plaçant le fichier dans le répertoire src/Controller
.
Si votre contrôleur fait des choses très basiques, vous pouvez étendre la classe ControllerBase
pour gagner du temps. Depuis cette classe vous aurez accès au Conteneur de Services, à la configuration, etc.
Pour des questions d’optimisation et de rationalisation, si votre contrôleur a une vraie logique métier, n’étendez pas ControllerBase
mais implémentez votre contrôleur à partir de rien.
Il est possible de passer des arguments aux routes si vous jetez un œil à la page de documentation de drupal.org pour y trouver un exemple.
Pour modifier des routes existantes il suffit d’implémenter un service qui étend la classe RouteSubscriberBase
et de surcharger la méthode alterRoutes()
pour pouvoir modifier le comportement des routes de base.
Il devient de cette façon possible de remplacer très simplement l’URL d’une route de connexion par exemple.
Commentaires
Votre commentaire
À propos de Julien
Co-fondateur - Scrum master & Expert technique
Utilisateur de Drupal depuis 2008, j’ai fait mes armes comme développeur chez Commerce Guys puis me suis mis à encadrer les nouveaux arrivants avant de donner des formations, participer aux avant ventes et accompagner les équipes au passage à Scrum.
Je suis impliqué dans la communauté française de Drupal depuis 2009, j’ai été tour à tour président puis vice-président de l’association Drupal France et francophonie entre 2011 et 2013.
Salut,
Ta demande n'est pas très claire pour moi. Si tu souhaites valider les paramètres pour chaque page chargée, il faut implémenter un
EventSubscriber
pour réagir à l'événementKernelEvents::REQUEST
. C'est l'un des tous premiers événements sur lequel il est possible de se greffer.Selon ton besoin de fond il serait probablement plus facile de te suggérer exactement ce dont tu as besoin mais ce n'est probablement pas dans les commentaires d'un billet de blog qu'on arrivera à se comprendre. Je t'invite donc à rejoindre la communauté française sur Slack en suivant les instructions qu'on peut trouver sur le site officiel de Drupal (salon #drupal-france).
A+