logo

Types de blocs supplémentaires (EBT) – Nouvelle expérience de Layout Builder❗

Types de blocs supplémentaires (EBT) – types de blocs stylisés et personnalisables : diaporamas, onglets, cartes, accordéons et bien d’autres. Paramètres intégrés pour l’arrière-plan, la boîte DOM, les plugins JavaScript. Découvrez dès aujourd’hui le futur de la création de mises en page.

Démo des modules EBT Télécharger les modules EBT

❗Types de paragraphes supplémentaires (EPT) – Nouvelle expérience Paragraphes

Types de paragraphes supplémentaires (EPT) – ensemble de modules basé sur les paragraphes analogiques.

Démo des modules EPT Télécharger les modules EPT

Défilement

Création d’un style d’affichage Views pour Drupal

05/07/2025, by Ivan

Menu

Vue d'ensemble des systèmes d'événements

Les systèmes d'événements sont utilisés dans de nombreuses applications complexes comme un moyen permettant aux extensions de modifier le fonctionnement du système. Un système d'événements peut être implémenté de différentes manières, mais en général les concepts et composants qui le constituent sont les mêmes.

  • Abonnés aux événements (Event Subscribers) – parfois appelés « auditeurs », ce sont des méthodes ou fonctions appelables qui réagissent à un événement diffusé dans tout le registre des événements.
  • Registre des événements (Event Registry) – endroit où les abonnés aux événements sont collectés et triés.
  • Dispatcheur d'événements (Event Dispatcher) – mécanisme par lequel un événement est déclenché ou « envoyé » dans tout le système.
  • Contexte de l'événement (Event Context) – de nombreux événements nécessitent un ensemble de données spécifique important pour les abonnés à l'événement. Cela peut être une simple valeur passée à l'abonné, ou quelque chose de plus complexe, comme une classe spécialement créée contenant les données pertinentes.

Hooks Drupal

Pendant la majeure partie de son existence, Drupal a eu un système rudimentaire d'événements via les « hooks ». Voyons comment le concept de « hooks » se décompose en ces 4 éléments du système d'événements.

  • Abonnés aux événements – les hooks Drupal sont enregistrés dans le système par la définition d'une fonction avec un nom spécifique. Par exemple, pour s’abonner à l’événement « hook_my_event_name », il faut définir une fonction nommée myprefix_my_event_name(), où « myprefix » est le nom de votre module ou thème.
  • Registre des événements – les hooks Drupal sont stockés dans le panier « cache_bootstrap » sous l’identifiant « module_implements ». C’est simplement un tableau des modules qui implémentent un hook donné, identifié par le nom même du hook.
  • Dispatcheur d'événements – les hooks sont déclenchés différemment dans Drupal 7 et Drupal 8 :

        1) Drupal 7 : les hooks sont appelés via la fonction module_invoke_all()
        2) Drupal 8 : les hooks sont appelés via la méthode de service \Drupal::moduleHandler()->invokeAll().

  • Contexte de l'événement – le contexte est passé à l'abonné via des paramètres. Par exemple, cette invocation exécutera toutes les implémentations de "hook_my_event_name" en passant le paramètre $some_arbitrary_parameter :

        1) Drupal 7 : module_invoke_all('my_event_name', $some_arbitrary_parameter);
        2) Drupal 8 : \Drupal::moduleHandler()->invokeAll('my_event_name', [$some_arbitrary_parameter]);

Quelques inconvénients de l’approche « hooks » pour les événements :

  • Les événements ne sont enregistrés qu’au moment de la reconstruction du cache.

En général, Drupal recherche les nouveaux hooks seulement lors de la construction de certains caches. Cela signifie que pour implémenter un nouveau hook sur votre site, vous devez reconstruire certains caches en fonction du hook que vous implémentez.

  • Un seul abonnement par module pour chaque événement.

Comme les hooks sont définis par des noms de fonction très spécifiques, chaque module ou thème ne peut implémenter l'événement qu’une seule fois. C’est une limitation arbitraire par rapport à d’autres systèmes d’événements.

  • Impossible de définir facilement l’ordre d’exécution des événements.

Drupal définit l’ordre des abonnés aux événements selon le poids des modules dans le système. Les modules et thèmes ont un « poids » qui détermine leur ordre de chargement et donc l’ordre dans lequel leurs événements sont déclenchés. Pour contourner ce problème, Drupal 7 a introduit « hook_module_implements_alter », un second hook sur lequel votre module doit s’abonner si vous voulez modifier l’ordre d’exécution des hooks sans changer le poids du module.

Avec la base Symfony dans Drupal 8, un autre système d’événements existe désormais, généralement meilleur dans la plupart des cas. Bien que peu d’événements soient déclenchés dans le noyau Drupal 8, de nombreux modules ont commencé à utiliser ce système.

Événements Drupal 8

Les événements dans Drupal 8 ressemblent beaucoup aux événements Symfony. Voyons comment ils se répartissent selon notre liste des composants du système d’événements.

  • Abonnés aux événements – une classe qui implémente \Symfony\Component\EventDispatcher\EventSubscriberInterface.
  • Dispatcheur d'événements – une classe qui implémente \Symfony\Component\EventDispatcher\EventDispatcherInterface. En général, au moins une instance de dispatcheur d’événements est fournie comme service au système, mais d’autres peuvent être créés si nécessaire.
  • Registre des événements – le registre des abonnés est stocké dans l’objet « Event Dispatcher » sous forme d’un tableau contenant le nom de l’événement et sa priorité (ordre). Lors de l’enregistrement d’un événement comme service (voir exemples), il est enregistré dans le dispatcheur global accessible.
  • Contexte de l'événement – une classe qui étend \Symfony\Component\EventDispatcher\Event. En général, chaque extension qui déclenche son propre événement crée un nouveau type de classe Event contenant les données nécessaires pour les abonnés.

Apprendre à utiliser les événements dans Drupal 8 vous aidera à mieux comprendre le développement de modules personnalisés et vous préparera à un futur où les événements remplaceront (espérons-le) les hooks. Créons donc un module personnalisé qui montre comment utiliser chacun de ces composants d’événements dans Drupal 8.

Mon premier abonné aux événements Drupal 8

Créons notre premier abonné aux événements dans Drupal 8, en utilisant quelques événements de base. J’aime commencer simple, donc nous allons créer un abonné qui affiche un message à l’utilisateur lorsqu’un objet Config est enregistré ou supprimé.

Première chose, il nous faut un module pour faire ce travail. Je l’ai nommé custom_events.

name: Custom Events
type: module
description: Custom/Example event work.
core: 8.x
package: Custom

Ensuite, nous voulons enregistrer un nouvel abonné aux événements dans Drupal. Pour cela, il faut créer custom_events.services.yml. Si vous venez de Drupal 7 et que vous connaissez le système de hooks, pensez à cette étape comme écrire la fonction "hook_my_event_name" dans votre module ou thème.

services:
  # Nom de ce service.
  my_config_events_subscriber:
    # Classe de l’abonné aux événements qui écoutera les événements.
    class: '\Drupal\custom_events\EventSubscriber\ConfigEventsSubscriber'
    # Tagué comme event_subscriber pour enregistrer cet abonné auprès du service event_dispatch.
    tags:
      - { name: 'event_subscriber' }

C’est assez simple, mais détaillons un peu.

1) Nous définissons un nouveau service nommé "my_config_events_subscriber".
2) Nous définissons sa propriété "class" avec le nom complet de la nouvelle classe PHP que nous allons créer.
3) Nous définissons la propriété « tags » avec un tag nommé « event_subscriber ». C’est ainsi que le service est enregistré comme abonné aux événements dans le système.

Alternativement, vous pouvez utiliser le nom de la classe PHP de l’abonné (sans antislash initial) comme nom de service et omettre la propriété « class », par exemple :

services:
  # Nom du service, utilisant la classe d’abonné aux événements qui écoutera les événements.
  Drupal\custom_events\EventSubscriber\ConfigEventsSubscriber:
    tags:
      - { name: 'event_subscriber' }

Maintenant, il ne reste plus qu’à écrire la classe d’abonné aux événements. Cette classe doit répondre à plusieurs exigences :

1. Elle doit implémenter l’interface EventSubscriberInterface.
2. Elle doit posséder la méthode getSubscribedEvents() qui retourne un tableau. Les clés de ce tableau sont les noms des événements auxquels vous souhaitez vous abonner, et les valeurs sont les noms des méthodes correspondantes dans cette classe.

Voici notre classe d’abonné aux événements. Elle s’abonne aux événements dans la classe ConfigEvents et exécute une méthode locale pour chaque événement.

src/EventSubscriber/ConfigEventsSubscriber.php
<?php

namespace Drupal\custom_events\EventSubscriber;

use Drupal\Core\Config\ConfigCrudEvent;
use Drupal\Core\Config\ConfigEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
 * Classe ConfigEventsSubscriber.
 *
 * @package Drupal\custom_events\EventSubscriber
 */
class ConfigEventsSubscriber implements EventSubscriberInterface {

  /**
   * {@inheritdoc}
   *
   * @return array
   *   Noms des événements à écouter, et méthodes à exécuter.
   */
  public static function getSubscribedEvents() {
    return [
      ConfigEvents::SAVE => 'configSave',
      ConfigEvents::DELETE => 'configDelete',
    ];
  }

  /**
   * Réagit à l’enregistrement d’un objet config.
   *
   * @param \Drupal\Core\Config\ConfigCrudEvent $event
   *   Événement CRUD sur la config.
   */
  public function configSave(ConfigCrudEvent $event) {
    $config = $event->getConfig();
    \Drupal::messenger()->addStatus('Config enregistrée : ' . $config->getName());
  }

  /**
   * Réagit à la suppression d’un objet config.
   *
   * @param \Drupal\Core\Config\ConfigCrudEvent $event
   *   Événement CRUD sur la config.
   */
  public function configDelete(ConfigCrudEvent $event) {
    $config = $event->getConfig();
    \Drupal::messenger()->addStatus('Config supprimée : ' . $config->getName());
  }

}

C’est tout ! Cela paraît simple, mais examinons quelques points importants :

  • Nous implémentons l’interface EventSubscriberInterface.
  • Nous implémentons la méthode getSubscribedEvents(). Cette méthode retourne un tableau associatif événement => méthode.
  • Dans configSave() et configDelete(), nous attendons un objet de type ConfigCrudEvent. Cet objet possède la méthode getConfig() qui retourne l’objet Config concerné par l’événement.

Quelques questions que pourrait se poser un observateur attentif :

  • Qu’est-ce que ConfigEvents::SAVE et d’où vient-il ?

Lorsqu’on définit de nouveaux événements, il est courant de créer une constante globale dont la valeur est le nom de l’événement. Ici, \Drupal\Core\Config\ConfigEvents possède une constante SAVE dont la valeur est « config.save ».

  • Pourquoi attendons-nous un objet ConfigCrudEvent et comment le savons-nous ?

Lorsqu’on définit de nouveaux événements, il est aussi habituel de créer un type d’objet propre à cet événement, contenant les données nécessaires et offrant une API simple. Pour l’instant, nous avons déterminé l’objet attendu en étudiant le code source et la documentation publique.

Nous sommes maintenant prêts à activer le module et tester cet événement. Nous attendons que chaque fois qu’un objet config est enregistré ou supprimé, un message affiche son nom.

Comme les objets de configuration sont très courants dans Drupal 8, c’est facile à tester. La plupart des modules gèrent leurs paramètres via des objets config, donc il suffit d’installer et désinstaller un module pour voir quels objets config sont enregistrés et supprimés.

1. Installez le module "custom_events" vous-même.
2. Installez le module "Statistics"

events-1-installed

Message après l’installation du module Statistics.


On dirait que deux objets config ont été enregistrés ! Le premier est core.extension qui gère les modules et thèmes installés. Le second est statistics.settings.

3. Désinstallez le module Statistics

events-2-uninstalled

Message après la désinstallation du module Statistics.

Cette fois, on voit à la fois les événements SAVE et DELETE. L’objet config statistics.settings a été supprimé et core.extension a été enregistré.

Je dirais que c’est un succès ! Nous nous sommes abonnés avec succès à deux événements majeurs de Drupal.

Passons maintenant à la création de nos propres événements et à leur déclenchement pour que d’autres modules les utilisent.

Mon premier événement Drupal 8 et envoi d’événements

La première question est quel type d’événement nous allons envoyer et quand. Nous allons créer un événement pour le hook Drupal qui n’a pas encore d’événement dans le noyau : "hook_user_login".

Commençons par créer une nouvelle classe qui étend Event, que nous appellerons UserLoginEvent. Assurons-nous aussi de fournir un nom d’événement global accessible aux abonnés.

src/Event/UserLoginEvent.php
<?php

namespace Drupal\custom_events\Event;

use Drupal\user\UserInterface;
use Symfony\Component\EventDispatcher\Event;

/**
 * Événement déclenché lorsqu’un utilisateur se connecte.
 */
class UserLoginEvent extends Event {

  const EVENT_NAME = 'custom_events_user_login';

  /**
   * Le compte utilisateur.
   *
   * @var \Drupal\user\UserInterface
   */
  public $account;

  /**
   * Constructeur.
   *
   * @param \Drupal\user\UserInterface $account
   *   Le compte utilisateur connecté.
   */
  public function __construct(UserInterface $account) {
    $this->account = $account;
  }

}
  • UserLoginEvent::EVENT_NAME est une constante avec la valeur « custom_events_user_login ». C’est le nom de notre nouvel événement personnalisé.
  • Le constructeur attend un objet UserInterface et le stocke comme propriété de l’événement. Cela rendra l’objet $account accessible aux abonnés.

Et c’est tout !

Maintenant, nous devons seulement déclencher notre nouvel événement. Nous allons le faire dans "hook_user_login". Créons custom_events.module.

<?php

/**
 * @file
 * Contient custom_events.module.
 */

use Drupal\custom_events\Event\UserLoginEvent;

/**
 * Implémente hook_user_login().
 */
function custom_events_user_login($account) {
  // Instancie notre événement.
  $event = new UserLoginEvent($account);

  // Récupère le service event_dispatcher et déclenche l’événement.
  $event_dispatcher = \Drupal::service('event_dispatcher');
  $event_dispatcher->dispatch(UserLoginEvent::EVENT_NAME, $event);
}

Dans notre implémentation de "hook_user_login", nous faisons juste quelques opérations pour déclencher notre nouvel événement :

1. Création d’un nouvel objet UserLoginEvent en fournissant le $account disponible dans le hook.
2. Récupération du service event_dispatcher.
3. Appel de la méthode dispatch() du service event_dispatcher, avec le nom de l’événement (UserLoginEvent::EVENT_NAME) et l’objet événement créé ($event).

Voilà ! Nous envoyons maintenant notre événement personnalisé quand un utilisateur se connecte à Drupal.

Poursuivons en terminant notre exemple avec un abonné à cet événement. Commençons par mettre à jour notre fichier services.yml en ajoutant l’abonné que nous allons écrire.

services:
  # Nom de ce service.
  my_config_events_subscriber:
    class: '\Drupal\custom_events\EventSubscriber\ConfigEventsSubscriber'
    tags:
      - { name: 'event_subscriber' }

  # Abonné à l’événement déclenché dans hook_user_login.
  custom_events_user_login:
    class: '\Drupal\custom_events\EventSubscriber\UserLoginSubscriber'
    tags:
      - { name: 'event_subscriber' }

Comme précédemment, nous définissons un nouveau service et le taguons comme event_subscriber. Maintenant, écrivons cette classe EventSubscriber.

src/EventSubscriber/UserLoginSubscriber.php
<?php

namespace Drupal\custom_events\EventSubscriber;

use Drupal\custom_events\Event\UserLoginEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
 * Classe UserLoginSubscriber.
 *
 * @package Drupal\custom_events\EventSubscriber
 */
class UserLoginSubscriber implements EventSubscriberInterface {

  /**
   * Connexion à la base de données.
   *
   * @var \Drupal\Core\Database\Connection
   */
  protected $database;

  /**
   * Formatteur de date.
   *
   * @var \Drupal\Core\Datetime\DateFormatterInterface
   */
  protected $dateFormatter;

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents() {
    return [
      // Constante statique de classe => méthode de cette classe.
      UserLoginEvent::EVENT_NAME => 'onUserLogin',
    ];
  }

  /**
   * S’abonne à l’événement de connexion utilisateur.
   *
   * @param \Drupal\custom_events\Event\UserLoginEvent $event
   *   Objet événement.
   */
  public function onUserLogin(UserLoginEvent $event) {
    $database = \Drupal::database();
    $dateFormatter = \Drupal::service('date.formatter');

    $account_created = $database->select('users_field_data', 'ud')
      ->fields('ud', ['created'])
      ->condition('ud.uid', $event->account->id())
      ->execute()
      ->fetchField();

    \Drupal::messenger()->addStatus(t('Bienvenue, votre compte a été créé le %created_date.', [
      '%created_date' => $dateFormatter->format($account_created, 'short'),
    ]));
  }

}

Explications :

1. Nous nous abonnons à l’événement nommé UserLoginEvent::EVENT_NAME avec la méthode onUserLogin() (méthode que nous avons créée).
2. Dans onUserLogin, nous accédons à la propriété $account (l’utilisateur qui vient de se connecter) de l’objet $event et faisons quelques opérations.
3. Lorsqu’un utilisateur se connecte, il doit voir un message indiquant la date de création de son compte.

events-3-login

Message après connexion.

Voilà ! Nous avons déclenché un nouvel événement personnalisé et nous nous y sommes abonnés. Nous sommes doués !

Priorités des abonnés aux événements

Une autre excellente fonctionnalité du système « Événements » est la possibilité pour un abonné de définir sa propre priorité dans l’abonné lui-même, au lieu de modifier le poids du module entier ou d’utiliser un autre hook pour changer la priorité (comme avec les hooks).

C’est très simple à faire, mais pour bien le démontrer, nous devons écrire un autre abonné aux événements alors que nous en avons déjà un. Créons « AnotherConfigEventSubscriber » et fixons les priorités de ses auditeurs.

Commençons par enregistrer ce nouvel abonné dans notre fichier services.yml :

services:
  my_config_events_subscriber:
    class: '\Drupal\custom_events\EventSubscriber\ConfigEventsSubscriber'
    tags:
      - { name: 'event_subscriber' }

  custom_events_user_login:
    class: '\Drupal\custom_events\EventSubscriber\UserLoginSubscriber'
    tags:
      - { name: 'event_subscriber' }

  another_config_events_subscriber:
    class: '\Drupal\custom_events\EventSubscriber\AnotherConfigEventsSubscriber'
    tags:
      - { name: 'event_subscriber' }

Ensuite, écrivons AnotherConfigEventSubscriber.php :

<?php

namespace Drupal\custom_events\EventSubscriber;

use Drupal\Core\Config\ConfigCrudEvent;
use Drupal\Core\Config\ConfigEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
 * Classe AnotherConfigEventsSubscriber.
 *
 * @package Drupal\custom_events\EventSubscriber
 */
class AnotherConfigEventsSubscriber implements EventSubscriberInterface {

  /**
   * {@inheritdoc}
   *
   * @return array
   *   Noms des événements à écouter et méthodes à exécuter.
   */
  public static function getSubscribedEvents() {
    return [
      ConfigEvents::SAVE => ['configSave', 100],
      ConfigEvents::DELETE => ['configDelete', -100],
    ];
  }

  /**
   * Réagit à l’enregistrement d’un objet config.
   *
   * @param \Drupal\Core\Config\ConfigCrudEvent $event
   *   Événement CRUD sur la config.
   */
  public function configSave(ConfigCrudEvent $event) {
    $config = $event->getConfig();
    \Drupal::messenger()->addStatus('(Autre) Config enregistrée : ' . $config->getName());
  }

  /**
   * Réagit à la suppression d’un objet config.
   *
   * @param \Drupal\Core\Config\ConfigCrudEvent $event
   *   Événement CRUD sur la config.
   */
  public function configDelete(ConfigCrudEvent $event) {
    $config = $event->getConfig();
    \Drupal::messenger()->addStatus('(Autre) Config supprimée : ' . $config->getName());
  }

}

La seule différence importante est que nous avons modifié le tableau retourné dans getSubscribedEvents(). Au lieu d’avoir comme valeur une chaîne (le nom de la méthode locale), c’est maintenant un tableau où le premier élément est le nom de la méthode et le deuxième élément est la priorité de cet auditeur.

Nous sommes donc passés de :

public static function getSubscribedEvents() {
  return [
    ConfigEvents::SAVE => 'configSave',
    ConfigEvents::DELETE => 'configDelete',
  ];
}

à :

public static function getSubscribedEvents() {
  return [
    ConfigEvents::SAVE => ['configSave', 100],
    ConfigEvents::DELETE => ['configDelete', -100],
  ];
}

Résultats attendus :

  • AnotherConfigEventSubscriber::configSave() a une priorité très élevée, donc il sera exécuté avant ConfigEventsSubscriber::configSave().
  • AnotherConfigEventSubscriber::configDelete() a une priorité très basse, donc il sera exécuté après ConfigEventsSubscriber::configDelete().

Voyons l’événement SAVE en action en réinstallant le module Statistics.

events-4-installed-priorities

Installation du module Statistics et consultation des messages.

Parfait ! Notre nouvel auditeur à ConfigEvents::SAVE a été appelé avant celui que nous avons écrit auparavant. Maintenant, désinstallons le module Statistics et observons l’événement DELETE.

events-5-uninstalled-priorities

Désinstallation du module Statistics et consultation des messages.

Excellent également ! Notre nouvel auditeur à ConfigEvents::DELETE a été appelé après l’autre, car il a une priorité plus basse.

Note : Lorsqu’un abonné est enregistré sans priorité, celle-ci est par défaut à 0.

Liens :