Aller au contenu principal
Julien Dubois

Drupal 10 : comment fonctionne le routage

Ce billet a été produit dans le contexte d'Happyculture, il a pu être écrit en collaboration.

Cet article a été initialement rédigé pour Drupal 8 mais son contenu est toujours d'actualité pour Drupal 9 et Drupal 10.


Découvrir la façon dont Drupal traite les requêtes entrantes et détermine la réponse à y apporter.

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 :

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 :

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.

Pour rebondir