Event API

Depuis toujours , Drupal fournit une multitude de hooks qui donnent la possibilité de procéder à un traitement spécifique lié à une action donnée.

Avant d'attaquer l'Api Event, Il faut préciser que les hooks ne sont que des fonctions PHP qui permettent d’interagir avec le cœur, les modules ainsi que les thèmes de Drupal.

Exemple des hooks:

/**
 * Implements hook_ENTITY_insert().
 *
 */
function hook_entity_insert(Drupal\Core\Entity\EntityInterface $entity) {  
  if ($entity->getEntityType()->id() == 'node' && $entity->bundle() == "article") {  
   $entity->setTitle($entity->label() . ' ' . date("Y/m/d"));  
   $entity->save();  
  }  
}

Dans cet exemple, le hookENTITYinsert nous a permis d'interagir avec l'entité Node, en modifiant cette dernière avant qu'elle soit sauvegardée dans la base de donnée. Il faut pas nier que le système des hooks est surpuissant, vous allez vous demander pourquoi alors adopter l'API Event !

Après l'arrivée de Drupal 8 ....

Comme on l'avait vu dans la section précédente; le système des hooks à fait ses preuves dans les anciennes versions de Drupal, mais avec l'arrivée de Drupal 8, le plus grand défi était de refaire le CMS d'une façon à ce qu'il adopte le paradigme d'orienté objet, dans ce cadre Drupal a introduit l'API Event de Symfony dans sa version 8 toute en gardant le système des hooks qui est supposé être totalement supprimer dans la version 9 de Drupal.

Mais comment peut-on utiliser l'API Event sur Drupal 8 ?

Théoriquement, il existe un registre d'événements appelé Event Registry

  • On peut déclarer ou diffuser un événement dans l'Event Registry pour permettre aux autres modules d'intervenir et changer notre propre module toute en utilisant la classe EventDispatcher.
  • On peut souscrire à un événement de l'Event Registry; c'est à dire écouter ou surveiller un événement donné pour déclencher une action précise en utilisant la classe EventSubscriber.

Un pas vers la pratique de l'API Event :)

1. Event dispatcher

Comme on l'avait déjà cité, l'API Event nous permet de propager un événement dans notre site afin qu'on puisse l'écouter par un listener

Créons ensemble notre module Custom events qui va contenir tous nos travaux sur l'Event API:

custom_events.info.yml
name: Custom events
type: module
description: Example events distpacher and subscriber.
core: 8.x	
package: Custom

Comment définir notre propre Événement ?

Il faut définir la classe d'événement dans le répertoire src/Event dans la racine du module, dans notre cas c'est custom_events/src/Event/ExampleEvent.php :

<?php  
  
namespace Drupal\project\Event;  
  
use Symfony\Component\EventDispatcher\Event;  
  
class ExampleEvent extends Event  
{  
  const SUBMIT = 'event.submit';  
 protected $referenceID;  
  
 public function __construct($referenceID)  
 {  $this->referenceID = $referenceID;  
  }  
  
  public function getReferenceID()  
 {  return $this->referenceID;  
  }  
  
  public function myEventMessage()  
 {  return "Ceci est un exemple d'événement.";  
  }  
}
  • Il faut que notre classe hérite strictement de la classe Event qui existe dans le cœur de Drupal et notamment dans ce namespace Symfony\Component\EventDispatcher\Event
  • Notre classe peut contenir nos propres méthodes qui seront accessible par la suite une fois notre événement est propagé ou écouté.
  • Le constructeur sert à définir la variable $referenceID qui va être accessible par l'Event Subscriber, une fois notre événement est propagé le constructeur va prendre l'ID $referenceID.

Comment DISPATCHER notre événement ?

C'est ici que se cache la puissance de API Event, notre événement peut être propager n'importe où dans notre application, comment? voyons voir ensemble :

<?php

// On utilise le namespace de notre event (ExampleEvent)
use Drupal\example_events\ExampleEvent;

// On charge le service event_dispatcher
$dispatcher = \Drupal::service('event_dispatcher');

// On crée une nouvelle instance de notre event (ExampleEvent) 
$event = new ExampleEvent($form_state->getValue('name'));

// On dispatche l'événement via la méthode "dispatch" et on passe dedant le nom de l'événement qu'on souhaite propager et l'objet d'événement '$event' comme paramètre. 

$dispatcher->dispatch(ExampleEvent::SUBMIT, $event);

Le code snippet si-dessus montre comment dispatcher un événement n'importe où dans notre site.

Dans ce tutoriel, on va créer un formulaire dans lequel on dispatchera notre événement et plus précisément dans le callback submitForm, on verra ça toute de suite :

<?php  
/**  
 * @file  
  * Contains \Drupal\example_events\Form\DemoEventDispatchForm.  
 */
namespace Drupal\project\Form;  
use Drupal\Core\Form\FormBase;  
use Drupal\Core\Form\FormStateInterface;  
use Drupal\example_events\Event\ExampleEvent;  
/**  
 * Class DemoEventDispatchForm. 
 *  
 * @package Drupal\example_events\Form  
 */
 class EventDispatchForm extends FormBase {  
  /**  
 * {@inheritdoc}  
 */  
 public function getFormId() {  
  return 'event_dispatch_form';  
  }  
  /**  
 * {@inheritdoc}  
 */  
 public function buildForm(array $form, FormStateInterface $form_state) {  
  $form['reference'] = array(  
  '#type' => 'textfield',  
  '#title' => $this->t('Reference'),  
  '#description' => $this->t("Veuillez écrire quelque chose qui servira comme object d'événement"),  
  '#maxlength' => 64,  
  '#size' => 64,  
  );  
  $form['dispatch_action'] = array(  
  '#type' => 'submit',  
  '#value' => $this->t('Dispatcher'),  
  );  
 return $form;  
  }  
 
 /**  
 * {@inheritdoc}  
 */  
 public function submitForm(array &$form, FormStateInterface $form_state) {  
  
  // On dispatche notre événement
  $dispatcher = \Drupal::service('event_dispatcher');  
  $event = new ExampleEvent($form_state->getValue('reference'));  
  $dispatcher->dispatch(ExampleEvent::SUBMIT, $event);  
  }  
}

Il est temps de créer notre première Route sur laquelle on affichera notre formulaire:

example_events.event_dispatcher_form:  
  path: 'event-dispatch-form'  
  defaults:  
    _form: '\Drupal\example_events\Form\EventDispatchForm'  
  _title: 'Event Dispatcher Form'  
  requirements:  
    _permission: 'access content'

Parfait, jusqu'ici nous avons réussi à créer notre Event et le dispatcher une fois que notre formulaire est enregistré (submitForm callback).

2. Event Subscriber

Comment souscrire à notre événement ?

2.1 Création de la classe de l'Event Subscriber
Regardons ensemble maintenant comment on peut souscrire à l'événement qu'on a déjà créé (ExampleEvent).

Pour souscrire à un événement existant, il faudra créer la classe de l'Event Subscriber sur /ModuleRacine/src/EventSubscriber/, dans notre example c'est sur /example_events/src/EventSubscriber/ExampleEventSubscriber

<?php  
/**  
 * @file  
  * Contains \Drupal\project\ExampleEventSubscriber.  
 */
namespace Drupal\example_events\EventSubscriber;  
use Drupal\Core\Config\ConfigCrudEvent;  
use Drupal\Core\Config\ConfigEvents;  
use Drupal\example_events\Event\ExampleEvent;  
use Symfony\Component\EventDispatcher\EventSubscriberInterface;  

/**  
 * Class ExampleEventSubscriber. 
 */
class ExampleEventSubscriber implements EventSubscriberInterface {  

  /**  
 * {@inheritdoc}  
 */  
public static function getSubscribedEvents() {  
	return [ 
	  ConfigEvents::SAVE => ['savingConfig', 800],  
	  ExampleEvent::SUBMIT => array('executeAction', 800)  
	 ];
 }  
  
 /**  
 * Subscriber Callback for the event. 
 */  
public function executeAction(ExampleEvent $event) {  
 \Drupal::messenger()->addStatus(t("Vous avez souscrit à l'événement ExampleEvent qui a été dispatché lors de l'enregistrement du formulaire avec " . $event->getReferenceID() . " comme référence "));  
  }  
  
 /**  
 * Subscriber Callback for the event.
 */  
 
public function savingConfig(ConfigCrudEvent $event) {  
 \Drupal::messenger()->addStatus(t("La configuration " . $event->getConfig()->getName() . " a été sauvegardée"));  
  }  
}

Top, regardons ensemble ce qu'on a fait dans cette classe :

  • Nous avons implémenté l'interface EventSubscriberInterface et utiliser le namespace Symfony\Component\EventDispatcher\EventSubscriberInterface.
  • Nous avons implémenté la méthode getSubscribedEvents qui existe dans l'interface déjà implémentée, et on a mit comme résultat un tableau qui contient le nom d’événement (clé) => le nom de méthode qui convient (valeur) + la clé de priorité.

Vous allez certainement vous demander d’où vient ConfigEvents::SAVE; c'est un événement existant par défaut dispatché par le cœur de Drupal. C'est une bonne pratique de définir les nouveaux événements qu'on est entrain de créer comme des constantes pour qu'ils soient globalement disponibles dans notre classe.

2.2 Taguer la classe de l'Event Subscriber par event_subscriber

services:
example_events.event_subscriber_example:
class: Drupal\example_events\EventSubscriber\ExampleEventSubscriber
tags:
- { name: 'event_subscriber' }

Ci-dessus nous avons créer notre premier service example_events.event_subscriber_example dans example_events.services.yml et on a tagué ce dernier par event_subscriber, comme ça notre service est enregistré comme un EventSubscriber dans le service container.

Voici à quoi ressemble notre formulaire après enregistrement d'une référence:

Code source du module disponible sur Github :)