Cet article a été initialement rédigé pour Drupal 8 mais son contenu est toujours d'actualité pour Drupal 9 et Drupal 10.
Se familiariser avec le concept d'événements.
L’une des forces de Drupal depuis sa création est la facilité d’utilisation de son extensibilité. Autrement dit, ses hooks. Implémenter un hook est très simple, il suffit de respecter une convention de nommage.
Si on regarde sous le capot, voici comment se fait l’invocation de ces hooks :
# Cron.php
class Cron implements CronInterface {
protected $moduleHandler; // The module handler service.
/**
* Invokes any cron handlers implementing hook_cron.
*/
protected function invokeCronHandlers() {
// Iterate through the modules calling their cron handlers (if any):
foreach ($this->moduleHandler->getImplementations('cron') as $module) {
// Do not let an exception thrown by one module disturb another.
try {
$this->moduleHandler->invoke($module, 'cron');
}
catch (\Exception $e) {
watchdog_exception('cron', $e);
}
}
}
}
C'est la ligne $this->moduleHandler->invoke($module, 'cron');
qui fait le gros du travail. Les principales fonctions d'invocation des hooks ou de modification de données (drupal_alter()) sont pilotées par la classe ModuleHandler
. Pour les utiliser il faudra passer par le Service module_handler et appeler la méthode dont vous avez besoin. Si vous ne vous rappelez plus ce qu'est un Service, je vous invite à lire le billet sur Conteneur de Services et Services.
Beaucoup de hooks historiques ont été remplacés par des événements Symfony mais tous les hooks ne sont pas morts. Le motif des hooks est toujours très utile pour modifier les déclarations comme les formulaires via hook_form_FORM_ID_alter()
, ou les données passées à une clé de thème par exemple avec hook_preprocess_HOOK()
.
Dans le chapitre dédié aux annotations nous verrons qu’il y a d’autres mécanismes qui remplacent également les hook_*_info()
.
Hooks VS Events
Le principe des hooks peut être implémenté en suivant plusieurs design patterns différents, seulement voilà, Symfony vient avec sa propre implémentation grâce à sa classe EventDispatcher
. Lorsque l’on souhaite connaître tous les modules qui implémentent un hook, on déclenche (dispatch) un événement. Chaque module qui implémente ce hook se signale en souscrivant (subscribe) à cet événement et retourne les données appropriées.
On retrouve par exemple l’utilisation des événements dans la gestion des routes pour afficher une réponse HTML.
# core.services.yml
html_response.subscriber:
class: Drupal\Core\EventSubscriber\HtmlResponseSubscriber
tags:
- { name: event_subscriber }
# HtmlResponseSubscriber.php
class HtmlResponseSubscriber implements EventSubscriberInterface {
// The HTML response attachments processor service.
protected $htmlResponseAttachmentsProcessor;
public function __construct(AttachmentsResponseProcessorInterface $html_response_attachments_processor) {
$this->htmlResponseAttachmentsProcessor = $html_response_attachments_processor;
}
public static function getSubscribedEvents() {
// Appeler la méthode onRespond lorsque l'événement KernelEvents::RESPONSE survient.
$events[KernelEvents::RESPONSE][] = ['onRespond'];
return $events;
}
/**
* Processes attachments for HtmlResponse responses.
*/
public function onRespond(FilterResponseEvent $event) {
if (!$event->isMasterRequest()) {
return;
}
$response = $event->getResponse();
if (!$response instanceof HtmlResponse) {
return;
}
$event->setResponse($this->htmlResponseAttachmentsProcessor->processAttachments($response));
}
}
On déclare un Service qui implémente l’interface EventSubscriberInterface
, on y implémente la méthode getSubscribedEvents()
pour indiquer à quel(s) événement(s) réagir et quelle fonction appeler lorsque l’événement survient. L’appel à ce Service se fait automagiquement uniquement si on l’a tagué comme event_subscriber.