Aller au contenu principal
Stéphane Quantin

Main navigation

  • Portfolio
  • À Propos
  • Freelance Drupal
    • Audit de site Drupal
    • Contrat de maintenance Drupal
  • Blog
  • CV
  • Contact
  • Outils développeur
Languages
  • French
  • English

Fil d'Ariane

  1. Accueil
  2. Blog

Drupal 11 et les hooks orientés objet : vers des modules plus propres

Par StephaneQ , mar, 17 Déc 2024 - 10:56

Les hooks font partie de l’identité de Drupal depuis longtemps. Ils permettent à un module d’intervenir à des moments précis de l’exécution, d’altérer un formulaire, de réagir à une action, de modifier des données ou d’ajouter un comportement sans modifier directement le code de Drupal Core ou d’un module contribué.

Avec Drupal 11.1, ce mécanisme historique évolue : les hooks peuvent désormais être déclarés dans des classes, à l’aide de l’attribut #[Hook].

Ce changement ne supprime pas le système de hooks. Il le rapproche simplement des pratiques PHP modernes déjà utilisées ailleurs dans Drupal.

Le rôle historique des hooks dans Drupal

Dans Drupal, un hook est un point d’extension. Lorsqu’un événement ou une étape précise se produit, Drupal cherche les implémentations correspondantes dans les modules activés.

Historiquement, ces implémentations sont écrites sous forme de fonctions procédurales dans un fichier .module.

Par exemple, un module nommé example peut implémenter hook_help() avec une fonction example_help().

/**
   * Implements hook_help().
   */
  function example_help($route_name, \Drupal\Core\Routing\RouteMatchInterface $route_match): ?string {
    if ($route_name === 'help.page.example') {
      return 'Help text for the Example module.';
    }

    return NULL;
  }
  

Ce modèle est simple, direct et très efficace. Il a largement contribué à la souplesse de Drupal.

Mais avec le temps, certains fichiers .module deviennent difficiles à maintenir.

Les limites des gros fichiers .module

Sur un petit module, un fichier .module avec quelques hooks reste parfaitement lisible.

Sur un module plus ancien ou plus fonctionnel, la situation peut vite se dégrader : plusieurs hooks, des alters, du preprocess, des callbacks, parfois de la logique métier, et beaucoup de code dans un seul fichier.

Le problème n’est pas le hook lui-même. Le problème vient surtout de l’organisation.

  • le fichier peut devenir très long ;
  • les responsabilités sont parfois mélangées ;
  • le typage et l’autocomplétion sont moins confortables ;
  • l’injection de dépendances n’est pas naturelle ;
  • la logique est plus difficile à tester proprement.

Drupal moderne repose déjà beaucoup sur les classes, les services, les plugins, les événements, les contrôleurs et les formulaires objet. Les hooks procéduraux faisaient encore partie des zones plus anciennes du modèle de développement.

Le principe des hooks en classes

Avec Drupal 11.1, un module peut placer ses implémentations de hooks dans une classe située dans le namespace Hook du module.

En pratique, cela donne un fichier de ce type :

modules/custom/example/src/Hook/ExampleHooks.php
  

La classe utilise ensuite l’attribut #[Hook] pour déclarer quelle méthode correspond à quel hook.

<?php

  namespace Drupal\example\Hook;

  use Drupal\Core\Hook\Attribute\Hook;
  use Drupal\Core\Routing\RouteMatchInterface;

  /**
   * Hook implementations for the Example module.
   */
  final class ExampleHooks {

    /**
     * Implements hook_help().
     */
    #[Hook('help')]
    public function help(string $route_name, RouteMatchInterface $route_match): ?string {
      if ($route_name === 'help.page.example') {
        return 'Help text for the Example module.';
      }

      return NULL;
    }

  }
  

Le nom de la méthode est libre. Ce qui compte, c’est l’attribut #[Hook('help')], qui indique à Drupal que cette méthode implémente hook_help().

Ce que l’attribut #[Hook] apporte

L’attribut #[Hook] permet de déclarer explicitement l’intention de la méthode.

Au lieu de dépendre uniquement d’une convention de nommage comme example_help(), Drupal peut découvrir une méthode de classe marquée comme implémentation d’un hook.

C’est une évolution intéressante pour plusieurs raisons.

D’abord, le code peut être mieux organisé. On peut regrouper les hooks dans une classe dédiée, ou répartir les responsabilités dans plusieurs classes selon les besoins du module.

Ensuite, les signatures de méthodes deviennent plus lisibles pour les IDE et les outils d’analyse statique. Le typage est plus naturel, et le code s’inscrit mieux dans le reste de l’architecture objet de Drupal.

Enfin, cette approche ouvre la voie à une meilleure testabilité. Toute la logique ne devrait pas vivre directement dans la méthode du hook, mais le fait d’être dans une classe rend plus simple l’extraction vers des services ou des méthodes dédiées.

Un exemple avec hook_form_alter

Un cas courant dans Drupal est hook_form_alter().

En version procédurale, on écrirait généralement :

/**
   * Implements hook_form_alter().
   */
  function example_form_alter(array &$form, \Drupal\Core\Form\FormStateInterface $form_state, string $form_id): void {
    if ($form_id === 'node_article_form') {
      $form['advanced']['#open'] = TRUE;
    }
  }
  

En version orientée objet avec Drupal 11.1, l’idée devient :

<?php

  namespace Drupal\example\Hook;

  use Drupal\Core\Form\FormStateInterface;
  use Drupal\Core\Hook\Attribute\Hook;

  /**
   * Form hook implementations for the Example module.
   */
  final class ExampleFormHooks {

    /**
     * Implements hook_form_alter().
     */
    #[Hook('form_alter')]
    public function formAlter(array &$form, FormStateInterface $form_state, string $form_id): void {
      if ($form_id === 'node_article_form') {
        $form['advanced']['#open'] = TRUE;
      }
    }

  }
  

Le comportement reste le même. Ce qui change, c’est la manière d’organiser l’implémentation.

Une migration à faire progressivement

Il ne faut pas interpréter cette évolution comme une obligation de tout convertir immédiatement.

Sur un module simple, avec un ou deux hooks très courts, garder un fichier .module peut rester acceptable, notamment si le module doit rester compatible avec des versions plus anciennes de Drupal.

Pour un module destiné uniquement à Drupal 11.1 ou supérieur, les hooks en classes deviennent en revanche une option très intéressante.

La bonne approche est pragmatique : convertir d’abord les hooks qui encombrent réellement le fichier .module, ou ceux qui gagneraient à être regroupés par responsabilité.

Il faut aussi tenir compte de la compatibilité. Un module contribué qui doit supporter Drupal 10 ou Drupal 11.0 ne peut pas basculer sans stratégie adaptée. Pour du code projet maîtrisé, la décision est plus simple si la version cible est clairement Drupal 11.1 ou plus récente.

Ce que cela ne change pas

Les hooks restent des hooks.

Ils ne deviennent pas automatiquement des événements Symfony, ni des plugins, ni des services métier. Le mécanisme de point d’extension reste celui de Drupal.

Il faut donc continuer à respecter les mêmes principes :

  • ne pas mettre trop de logique métier directement dans un hook ;
  • éviter les hooks trop longs ;
  • préserver les métadonnées de cache quand on modifie un rendu ;
  • respecter les signatures attendues ;
  • extraire la logique complexe dans des services dédiés.

L’attribut #[Hook] améliore l’organisation du code. Il ne remplace pas une architecture propre.

Conclusion

Avec les hooks orientés objet, Drupal conserve l’un de ses mécanismes les plus importants tout en l’intégrant mieux dans une approche PHP moderne.

Pour les développeurs Drupal, c’est une évolution discrète mais significative. Elle permet de réduire le poids des fichiers .module, de mieux structurer les responsabilités et de rapprocher les hooks du reste du code objet utilisé dans Drupal.

Il ne s’agit pas de convertir tout le code existant par principe. Mais pour les nouveaux modules Drupal 11.1+, ou pour les modules custom qui commencent à accumuler trop de hooks procéduraux, cette approche mérite clairement d’être adoptée.

Drupal garde donc son système de hooks, mais lui donne une forme plus propre, plus lisible et plus adaptée aux pratiques actuelles de développement PHP.

Étiquettes

  • Drupal
  • Drupal 11

Réseaux sociaux

  • Malt
  • codeur.com
  • 404Works
  • LinkedIn
  • Twitter
  • DoYouBuzz

Twitter

Tweets by @StephaneQ
RSS feed

Pied de page

  • Contact
  • Mentions légales
Propulsé par Drupal