
L'évolution du système de hooks de Drupal : des fonctions procédurales aux événements orientés objet
Si vous avez travaillé avec Drupal pendant un certain temps, vous avez certainement écrit des implémentations de hooks. Des fonctions comme mymodule_form_alter() ou mymodule_node_presave() sont profondément ancrées dans la façon dont les développeurs Drupal pensent à étendre le système. Mais si vous avez aussi travaillé avec Drupal 8 ou versions ultérieures, vous avez probablement remarqué autre chose : des abonnés aux événements, des définitions de services et des patrons orientés objet qui ne ressemblent en rien aux hooks traditionnels.
Ce n'est pas un hasard. Le modèle d'extensibilité de Drupal évolue depuis près d'une décennie, et comprendre d'où il vient — et où il se dirige — importe pour quiconque construit ou maintient des sites Drupal aujourd'hui. En résumé : les hooks ne disparaissent pas, mais ils ne sont plus la seule option. Les événements et les services gèrent maintenant plusieurs des mêmes tâches, souvent de façon plus efficace.
Voyons ensemble comment on en est arrivé là et ce que ça signifie pour votre code.
Le système de hooks original : simple, rapide et procédural
Dans Drupal 4 à 7, les hooks constituaient essentiellement tout le modèle d'extensibilité de Drupal. Le concept était d'une élégante simplicité : définir une convention de nommage, et Drupal trouvait et appelait votre code automatiquement.
Voici comment ça fonctionnait :
- Le cœur ou un module définissait un hook, comme hook_menu() ou hook_form_alter()
- Votre module l'implémentait en créant une fonction avec le nom de votre module comme préfixe : mymodule_menu() ou mymodule_form_alter()
- Drupal scannait ces fonctions, mettait les résultats en cache et les appelait au bon moment
Sous le capot, cela correspondait à des concepts de système d'événements même si ça n'en avait pas l'air :
- Répartiteur : module_invoke_all() ou drupal_alter() géraient l'appel des implémentations
- Registre : Un tableau en cache suivant quels modules implémentaient quels hooks
- Écouteurs : Vos fonctions mymodule_nomhook()
- Contexte : Les paramètres passés à votre fonction
Cette approche avait de vrais avantages. C'était facile à apprendre — vous écriviez simplement des fonctions. La performance était solide parce que Drupal mettait agressivement en cache le registre d'implémentations. Et l'intégration avec le bootstrap de Drupal faisait que les hooks se déclenchaient de façon prévisible.
Mais les limites sont devenues plus évidentes à mesure que PHP lui-même évoluait :
État global partout. Les fonctions hook récupéraient typiquement ce dont elles avaient besoin depuis des variables globales ou des appels statiques. Les tester de façon isolée était pénible au mieux.
Une seule implémentation par module. Si vous vouliez répondre au même hook de deux façons différentes depuis le même module, vous deviez tout mettre dans une seule fonction.
Ordonnancement implicite. L'ordre d'exécution dépendait du poids du module et de l'ordre d'installation, pas de priorités explicites. Déboguer « pourquoi ceci s'est exécuté avant cela? » signifiait fouiller dans les tables de la base de données.
Découverte dépendante du cache. Les nouvelles implémentations de hooks n'étaient pas enregistrées tant que vous ne reconstruisiez pas les caches. Pendant le développement, ça signifiait des vidages de cache constants.
Ce n'étaient pas des problèmes rédhibitoires quand Drupal était principalement une application PHP 5.2 et que l'écosystème PHP était majoritairement procédural. Mais à mesure que PHP s'orientait vers les espaces de noms, le chargement automatique et l'injection de dépendances, les hooks ont commencé à sembler de plus en plus déconnectés de la façon dont les développeurs écrivaient du PHP partout ailleurs.
Le virage architectural de Drupal 8
Drupal 8 (sorti en 2015) représentait le plus grand changement architectural de l'histoire de Drupal. Le cœur a adopté les composants Symfony, est passé à Composer pour la gestion des dépendances, et a embrassé les espaces de noms et le chargement automatique PSR-4. PHP n'était plus procédural d'abord, et Drupal non plus.
Le composant EventDispatcher de Symfony est venu avec le lot, apportant un vrai système d'événements orienté objet. Voici à quoi ça ressemble en pratique :
Les abonnés aux événements sont des classes PHP implémentant EventSubscriberInterface, enregistrées comme services :
// src/EventSubscriber/MyEventSubscriber.php
namespace Drupal\mymodule\EventSubscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
class MyEventSubscriber implements EventSubscriberInterface {
public static function getSubscribedEvents() {
return [
KernelEvents::REQUEST => ['onRequest', 100],
];
}
public function onRequest(RequestEvent $event) {
// Votre logique ici
}
}# mymodule.services.yml
services:
mymodule.event_subscriber:
class: Drupal\mymodule\EventSubscriber\MyEventSubscriber
tags:
- { name: event_subscriber }Comparez ça à une implémentation de hook :
// mymodule.module
function mymodule_form_alter(&$form, $form_state, $form_id) {
// Votre logique ici
}La version hook est plus courte, certes. Mais l'approche par abonné aux événements offre plusieurs avantages :
Injection de dépendances. Votre abonné est un service, donc vous pouvez injecter d'autres services via le constructeur. Plus besoin d'appels \Drupal::service() éparpillés dans votre code.
Priorités explicites. Ce 100 dans l'exemple ci-dessus? C'est la priorité. Vous contrôlez exactement quand votre abonné se déclenche par rapport aux autres.
Plusieurs abonnés par module. Vous pouvez enregistrer autant de classes d'abonnés que nécessaire, chacune gérant différents événements ou le même événement de différentes façons.
Testabilité. Puisque les abonnés sont simplement des classes avec des dépendances injectées, vous pouvez les tester unitairement en simulant ces dépendances.
Découverte automatique. Les nouveaux abonnés sont détectés via la compilation des services, pas par la reconstruction du cache d'un registre de hooks.
La réalité hybride : hooks et événements coexistants
Si les événements sont meilleurs, pourquoi Drupal 8 n'a-t-il pas simplement remplacé tous les hooks par des événements?
Deux raisons : la rétrocompatibilité et la performance.
Les hooks étaient (et sont) utilisés partout. Hooks d'entités, hooks de formulaires, modifications de tableaux de rendu — des milliers de modules contrib et de sites personnalisés en dépendaient. Tout remplacer d'un coup aurait brisé l'écosystème.
La performance était l'autre facteur. Le système de hooks avait été optimisé pendant des années. Les premières discussions sur la conversion des hooks en événements (voir le ticket de longue date #1509164 dans la file d'attente de Drupal) ont soulevé des préoccupations concernant le coût de l'instanciation d'objets et des appels au répartiteur pour les opérations fréquentes.
Donc Drupal 8 a été livré avec les deux systèmes fonctionnant en parallèle :
Les événements gèrent :
- Les événements du cycle de vie du kernel (requête, réponse, gestion des exceptions)
- Les changements de configuration
- Les événements de routage
- Plusieurs sous-systèmes plus récents comme Migrate, qui définit explicitement des événements comme PRE_IMPORT et POST_IMPORT
Les hooks gèrent :
- Les modifications de formulaires
- Les opérations CRUD sur les entités
- Les modifications des tableaux de rendu
- Les fonctions de thème et de prétraitement
- La plupart des points d'extension « legacy »
Cette approche hybride continue dans Drupal 9, 10 et 11. La politique officielle de dépréciation inclut maintenant des directives spécifiques pour déprécier les hooks — les marquer avec @deprecated, utiliser invokeDeprecated() pour déclencher des avertissements, et documenter les patrons de remplacement. Mais la dépréciation se fait graduellement, hook par hook.
Notre travail avec les équipes nous a appris que cet état hybride crée une réelle confusion pour les développeurs. Vous rencontrez un besoin d'étendre quelque chose, et ce n'est pas toujours évident si vous devez chercher un hook ou un événement. La réponse dépend souvent de quand ce sous-système a été écrit ou dernièrement refactorisé.
Quand utiliser les hooks vs les événements dans le développement Drupal
Voici un aperçu pratique basé sur les patrons actuels de Drupal 10/11 :
Utilisez les hooks quand :
- Vous modifiez des formulaires (hook_form_alter(), hook_form_FORM_ID_alter())
- Vous répondez aux opérations sur les entités et qu'aucun équivalent événement n'existe
- Vous modifiez des tableaux de rendu
- Vous travaillez avec le prétraitement de thème
- La documentation vous pointe spécifiquement vers un hook
Utilisez les événements quand :
- Vous travaillez avec le cycle de vie requête/réponse
- Vous répondez aux changements de configuration
- Vous construisez sur des sous-systèmes qui exposent des événements (Migrate, Rules, etc.)
- Vous créez de nouveaux points d'extension dans vos propres modules
- Vous avez besoin d'un contrôle fin sur la priorité d'exécution
- La testabilité est une priorité
Vous créez de nouveaux points d'extension? Préférez les événements. C'est la direction que prend Drupal, et ils sont plus faciles à documenter et maintenir. Des modules contrib comme Search API convertissent activement leurs hooks en événements, gardant les anciens hooks temporairement pour la rétrocompatibilité mais les marquant comme dépréciés.
Développements récents : hooks POO et la question de la performance
C'est ici que ça devient intéressant — et un peu compliqué.
En 2023-2024, le cœur de Drupal a commencé à implémenter les hooks en interne en utilisant le système d'événements. L'idée était d'apporter les avantages orientés objet aux implémentations de hooks : les enregistrer comme services, utiliser des attributs pour la découverte, et les faire passer par l'EventDispatcher.
Ça n'a pas fonctionné comme prévu.
Les gros sites Drupal peuvent avoir des centaines d'implémentations de hooks. Tout faire passer par l'EventDispatcher de Symfony a introduit un surcoût de performance mesurable. Pire, les collisions de noms entre les noms d'événements et les noms d'événements de hooks générés automatiquement causaient des plantages à l'exécution quand le répartiteur essayait d'appeler des écouteurs avec des signatures de méthodes incompatibles.
Le résultat? Une nouvelle initiative (ticket #3506930, ouvert début 2024) pour « séparer les hooks des événements ». L'objectif n'est pas d'abandonner complètement les patrons POO pour les hooks, mais de découpler la répartition des hooks de l'EventDispatcher tout en préservant certains avantages des patrons d'enregistrement modernes.
Cela nous dit quelque chose d'important sur la direction que prend Drupal : les hooks et les événements resteront des systèmes parallèles, chacun optimisé pour différents cas d'utilisation. Le rêve d'unifier tout sous l'EventDispatcher a été tempéré par la réalité pratique.
Complexités de compatibilité Symfony
Un autre détail qui vaut la peine d'être mentionné : l'EventDispatcher de Symfony a évolué à travers les versions, et Drupal a dû s'adapter.
Dans Symfony 4.3, la classe de base Symfony\Component\EventDispatcher\Event a été dépréciée en faveur de Symfony\Contracts\EventDispatcher\Event. Drupal 9.1 a introduit Drupal\Component\EventDispatcher\Event comme couche de compatibilité. À partir de Drupal 10, cette classe étend la version Symfony Contracts.
Pour les mainteneurs de modules contrib supportant plusieurs versions de Drupal, ça a créé des maux de tête. Le module Rules, par exemple, a dû gérer trois classes Event de base différentes à travers les versions Drupal 9 et 10, menant à des décisions maladroites sur les type-hints et des vérifications de version.
Nous avons constaté que les équipes qui maintiennent des modules contrib ont besoin de matrices de tests claires à travers les versions de Drupal, particulièrement quand elles travaillent avec du code relié aux événements. Les couches de rétrocompatibilité aident, mais elles n'éliminent pas complètement la complexité.
Patrons de migration : passer des hooks aux événements
Si vous maintenez un module qui expose des hooks et que vous envisagez une migration vers les événements, voici une approche pratique :
Étape 1 : Identifier les candidats. Tous les hooks n'ont pas besoin de devenir des événements. Concentrez-vous sur les points d'extension qui bénéficieraient de priorités, d'abonnés multiples ou d'une meilleure testabilité.
Étape 2 : Créer des événements parallèles. Définissez des classes d'événements et répartissez-les en parallèle avec les hooks existants. Ça donne aux utilisateurs le temps de migrer sans briser les implémentations existantes.
Étape 3 : Marquer les hooks comme dépréciés. Utilisez les annotations @deprecated et invokeDeprecated() pour avertir les utilisateurs. Documentez clairement l'événement de remplacement.
Étape 4 : Supprimer les hooks dans une version majeure. Donnez au moins un cycle de version majeure avant de supprimer complètement les hooks dépréciés.
L'approche de Search API est instructive ici. Leurs mainteneurs ont explicitement déclaré : « Les événements sont plus standards, auto-chargés, autonomes et orientés objet. » Mais ils gardent temporairement les deux mécanismes, privilégiant la rétrocompatibilité à la pureté architecturale.
Recommandations pour les projets Drupal actuels
Notre expérience montre que l'approche hybride nécessite des conventions d'équipe claires. Voici ce que nous suggérons :
Pour les nouveaux modules : Exposez des événements plutôt que des hooks pour les points d'extension personnalisés. Ils sont mieux documentés via le code (les classes d'événements sont auto-descriptives), plus faciles à tester, et s'alignent avec la direction que prend Drupal.
Pour les modules existants : Ne vous précipitez pas pour migrer des hooks fonctionnels. Si une approche basée sur les hooks fonctionne bien et qu'il n'y a pas de besoin pressant pour les fonctionnalités des événements, la stabilité vaut la peine d'être préservée.
Pour les constructions de sites : Suivez la documentation. Quand le cœur de Drupal ou un module contrib offre à la fois un hook et un événement pour le même objectif, l'événement est généralement préféré. Mais si seul un hook existe, utilisez-le sans culpabilité.
Pour la maintenance contrib : Si vous supportez à la fois Drupal 9 et 10, portez une attention particulière à l'héritage des classes d'événements. Testez à travers les versions, et considérez abstraire la gestion des événements derrière vos propres classes de base pour vous isoler des changements futurs.
Conclusion
Le modèle d'extensibilité de Drupal a parcouru un chemin considérable depuis les hooks procéduraux de Drupal 4-7. L'EventDispatcher de Symfony a apporté de vrais événements orientés objet à Drupal 8, offrant testabilité, priorités explicites et alignement avec les pratiques PHP plus larges. Mais les hooks n'ont pas disparu — ils ont coexisté, et les développements récents montrent qu'ils continueront d'exister comme un mécanisme séparé, optimisé pour la performance, plutôt qu'une mince couche au-dessus des événements.
Pour les développeurs, ça signifie apprendre les deux systèmes et comprendre quand chacun fait sens. Les hooks restent le bon choix pour plusieurs tâches courantes comme la modification de formulaires. Les événements sont préférables pour les nouveaux points d'extension et les situations où la testabilité et le contrôle des priorités importent.
Comprendre comment les systèmes de hooks et d'événements de Drupal interagissent peut faire une vraie différence dans les décisions d'architecture de modules. Si vous évaluez comment structurer les points d'extension d'un module personnalisé, ou que vous essayez de décider si migrer une API existante basée sur les hooks vers les événements, nous pouvons vous aider à réfléchir aux compromis pour votre situation spécifique.
https://www.rollin.ca/fr/services/developpement-drupal/
