Drupal 11 - Utiliser la nouvelle syntaxe Orientée Objet pour les Hooks

Implémentation orientée objet des hook

Drupal 11.1 va introduire une nouvelle façon d'implémenter les hooks, de manière orientée objet !

En pratique il suffira de créer un fichier MesHooks.php (un ou plusieurs fichiers, le nom est libre) dans le dossier src/Hook (cela peut être dans un sous dossier, mais le dossier de base doit être obligatoirement Hook pour être détecté par drupal) de votre module.

Ainsi pour implémenter le hook HOOK_form_alter, voici comment cela se passe : 

Je créé un fichier mon_module/src/Hook/FormHooks.php avec le contenu suivant :

<?php
namespace Drupal\mon_module\Hook;

use Drupal\Core\Hook\Attribute\Hook;

/**
 * Provides hook implementations for Form alterations.
 */
class FormHooks {

  /**
   * Alters a Drupal form before it is rendered.
   *
   * @param array $form
   *   An associative array containing the structure of the form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   An object containing the current state of the form.
   * @param string $form_id
   *   The unique identifier of the form being altered.
   */
  #[Hook('form_alter')]
  public static function formAlter(&$form, FormStateInterface $form_state, $form_id): void {
    ...
  }
  
  /**
   * Alters the programme bundle edit and add forms
   *
   * @param array $form
   *   The form structure to be altered.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   * @param string $form_id
   *   The unique identifier of the form.
   */
  #[Hook('form_node_programme_edit_form_alter')]
  #[Hook('form_node_programme_form_alter')]
  public function formProgrammeAlter(&$form, FormStateInterface $form_state, $form_id): void {
     ...
  }
}

c'est l'annotation 

#[Hook('form_alter')]

qui indiquera à drupal quel est le hook implémenté par la fonction qui suit (dont le nom est libre)

On peut aussi utiliser une même fonction pour plusieurs hooks, ici j'implémente les hooks HOOK_form_FORM_ID_alter pour mes formulaire d'ajout et d'édition de noeud pour mon type de contenu « programme ».

  #[Hook('form_node_programme_edit_form_alter')]
  #[Hook('form_node_programme_form_alter')]
  public function formProgrammeAlter(CommentInterface $comment) {
  ...
  }

La fin du fichier module ?

Presque, comme indiqué dans le change record, tous les hooks ne peuvent pas être implémenté de cette manière là. Certains doivent rester en procédural. Même si comme l'on voit sur ce nouveau changement du 12 novembre, la liste commence à se réduire (en barré, les hooks qui peuvent maintenant bénéficier de cette nouvelle syntaxe.

  • hook_cache_flush()
  • hook_module_preinstall()
  • hook_module_preuninstall()
  • hook_modules_installed()
  • hook_modules_uninstalled()
  • hook_install()
  • hook_post_update_NAME()
  • hook_requirements()
  • hook_schema()
  • hook_uninstall()
  • hook_update_last_removed()
  • hook_update_N()
  • hook_theme_suggestion_HOOK()
  • hook_theme_suggestions_HOOK_alter()

La liste devrait être mise à jour sur la page suivante : https://www.drupal.org/node/3442349

Note du 17/12/2024 : durant nos tests lors de l'upgrade vers drupal 11.1 avec Alan, il s'avère que les hooks HOOK_preprocess_ENTITY_TYPE doivent eux aussi rester en procédural. Nous avons choisi de rester en orienté objet, mais nous avons du laisser la définition du service et le hook_procedural pour faire l'appel au service.

Compatibilité avec Drupal 10 et 11.0

Cette syntaxe ne fonctionne qu'à partir de Drupal 11.1, qui devrait sortir en décembre 2024, mais bonne nouvelle, il est possible de l'utiliser dès aujourd'hui en attendant de faire l'upgrade. Par contre dommage, il faut pour cela garder l'implémentation original qui appellera notre classe.

On commence par ajouter notre classe à notre fichier mon_module.services.yml : 

services:
  Drupal\mon_module\Hook\FormHooks:
    class: Drupal\mon_module\Hook\FormHooks
    autowire: true

Et on ajoute l'appel à la classe dans mon_module.module : 

/**
 * Implements hook_form_alter().
 */
#[LegacyHook]
function mon_module_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  \Drupal::service(FormHooks::class)->formAlter($form, $form_state, $form_id);
}

/**
 * Implements hook_form_node_programme_edit_form_alter().
 */
#[LegacyHook]
function mon_module_form_node_programme_edit_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  \Drupal::service(FormHooks::class)->formProgrammeAlter($form, $form_state, $form_id);
}

/**
 * Implements hook_form_node_programme_form_alter().
 */
#[LegacyHook]
function mon_module_form_node_programme_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  \Drupal::service(FormHooks::class)->formProgrammeAlter($form, $form_state, $form_id);
}

Une fois la migration vers drupal 11.1 faite, ces deux ajustements pourront être supprimés.

À noter, pour que les annotations soient connues par Drupal (et que phpstan valide votre code), il est nécessaire d'utiliser le patch présent sur cet issue : https://www.drupal.org/project/drupal/issues/3482464 qui backport les déclarations d'annotations : https://git.drupalcode.org/project/drupal/-/merge_requests/9908.patch

Voir le change record : https://www.drupal.org/node/3442349

Petite astuce pour les performance

Nouveauté du 3 décembre 2024, il existe maintenant une annotation pour signaler à drupal qu'il n'est pas nécessaire de scanner un fichier à la recherche d'implémentation de hooks (principalement les fichiers .module) On est sur l'ordre de la micro-optimisation mais bon, ça peut servir.

Si vous n'utilisez aucun hook procédural (ou uniquement à visé de rétrocompatibilité pour les drupaux < 11.1, vous pouvez ajouter à votre fichier services.yml la clé suivante : 

parameters:
  module_name.hooks_converted: true

Si certains de vos fichiers contiennent encore des hooks procéduraux, alors placez-les tous au début de votre fichier, ajoutez à vos use

use Drupal\Core\Hook\Attribute\StopProceduralHookScan;

Et juste après votre dernier hook procédural, placez l'annotation suivante : 

#[StopProceduralHookScan]

Ainsi drupal saura qu'il n'a pas besoin de scanner le reste du fichier à la recherche de potentiels hooks.

Le change record : https://www.drupal.org/node/3490771

Contenus en rapport

Ajouter un commentaire

Ne sera pas publié
CAPTCHA
Désolé, pour ça, mais c'est le seul moyen pour éviter le spam...