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.

Schéma issu de la documentation drupal.org / CC BY-SA 2.0

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

Anonyme Mercredi 14 novembre 2018 - 15:04
Bonjour, j'ai une difficulté dans un module que je développe je suis un peu nouveau. Je voudrai pouvoir verifier les paramètres de l'url a chaque fois que l'url change, j'utilise hook_form pour certaines pages mais d'autres pages n'ont pas de formulaire que puis je utiliser pour pouvoir agir a chaque fois que l'url change.   Best
En réponse à par Anonyme
DuaelFr Mercredi 14 novembre 2018 - 15:40

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énement KernelEvents::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+

Votre commentaire

Le contenu de ce champ sera maintenu privé et ne sera pas affiché publiquement.
Votre adresse servira à afficher un Gravatar et à vous notifier des réponses. Votre commentaire sera anonymisé si ce billet est dépublié pendant plus de 3 mois.
Pour lutter contre le spam notre système enregistre votre adresse IP et votre adresse e-mail si vous la partagez.
Nous vous invitons à consulter notre politique de confidentialité pour comprendre les traitements faits de ces données et comment les rectifier.

À 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.