Event Subscriber e Event Dispatcher. Il sistema di gestione degli eventi in Drupal.
Panoramica dei sistemi di eventi
I sistemi di eventi vengono utilizzati in molte applicazioni complesse come un modo per consentire alle estensioni di modificare il funzionamento del sistema. Un sistema di eventi può essere implementato in diversi modi, ma in generale i concetti e i componenti che lo compongono sono simili.
- Sottoscrittori di eventi (Event Subscribers) – a volte chiamati “listener”, sono metodi o funzioni richiamabili che reagiscono a un evento diffuso in tutto il registro degli eventi.
- Registro degli eventi (Event Registry) – dove vengono raccolti e ordinati i sottoscrittori di eventi.
- Dispatcher degli eventi (Event Dispatcher) – il meccanismo con cui un evento viene iniziato o “inviato” all’interno del sistema.
- Contesto dell’evento (Event Context) – per molti eventi è richiesto un set specifico di dati, importante per i sottoscrittori dell’evento. Può essere un semplice valore passato al sottoscrittore o qualcosa di complesso, come una classe creata appositamente che contiene i dati pertinenti.
Hook di Drupal
Per gran parte della sua esistenza Drupal ha avuto un sistema rudimentale di eventi attraverso gli “hook”. Vediamo come il concetto di hook si suddivide in questi 4 elementi del sistema di eventi.
- Sottoscrittori di eventi – gli hook di Drupal vengono registrati nel sistema definendo una funzione con un nome specifico. Ad esempio, se vuoi sottoscriverti a un evento chiamato “hook_my_event_name”, devi definire una nuova funzione con il nome myprefix_my_event_name(), dove “myprefix” è il nome del tuo modulo o tema.
- Registro degli eventi – gli hook di Drupal vengono memorizzati nella cache “cache_boostrap” con l’identificatore “module_implements”. Questo è semplicemente un array di moduli che implementano l’hook, identificati dal nome dell’hook stesso.
- Dispatcher degli eventi – gli hook vengono inviati in modo diverso in Drupal 7 rispetto a Drupal 8:
1) Drupal 7: gli hook vengono inviati usando la funzione module_invoke_all()
2) Drupal 8: gli hook vengono inviati tramite il metodo del servizio \Drupal::moduleHandler()->invokeAll().
- Contesto dell’evento – il contesto viene passato al sottoscrittore tramite i parametri. Ad esempio, questa distribuzione eseguirà tutte le implementazioni di "hook_my_event_name" e passerà il parametro $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]);
Alcuni svantaggi dell’approccio “hook” agli eventi:
- Registra gli eventi solo durante la ricostruzione della cache.
In generale, Drupal cerca i nuovi hook solo quando costruisce determinate cache. Ciò significa che se vuoi implementare un nuovo hook sul tuo sito, dovrai ricostruire varie cache a seconda dell’hook che implementi.
- Può reagire a ogni evento solo una volta per modulo.
Poiché questi eventi vengono implementati definendo nomi di funzione molto specifici, in ogni modulo o tema può esserci solo una implementazione dell’evento. Questo è un limite arbitrario rispetto ad altri sistemi di eventi.
- Non è possibile determinare facilmente l’ordine degli eventi.
Drupal determina l’ordine dei sottoscrittori in base al “peso” dei moduli nella più ampia struttura del sistema. I moduli e i temi hanno un “peso” che determina l’ordine di caricamento, e quindi l’ordine con cui vengono eseguiti gli eventi. Un workaround a questo problema è stato introdotto più tardi in Drupal 7 con "hook_module_implements_alter".
Con l’integrazione di Symfony in Drupal 8 ora esiste un altro sistema di eventi. Un sistema migliore, nella maggior parte dei casi. Anche se nel core di Drupal 8 non vengono inviati molti eventi, molti moduli hanno iniziato a utilizzare questo sistema.
Eventi in Drupal 8
Gli eventi in Drupal 8 sono molto simili a quelli di Symfony. Vediamo come si scompongono nei nostri componenti del sistema di eventi.
- Sottoscrittori di eventi – una classe che implementa \Symfony\Component\EventDispatcher\EventSubscriberInterface.
- Dispatcher degli eventi – una classe che implementa \Symfony\Component\EventDispatcher\EventDispatcherInterface. Generalmente almeno un’istanza del dispatcher viene fornita come servizio per il sistema, ma possono essere creati altri dispatcher.
- Registro degli eventi – i sottoscrittori vengono memorizzati nel dispatcher come array contenente nome e priorità dell’evento (ordine). Quando un evento viene registrato come servizio, viene registrato nel dispatcher globale.
- Contesto dell’evento – una classe che estende \Symfony\Component\EventDispatcher\Event. Solitamente ogni estensione che invia un proprio evento crea una nuova classe Event che contiene i dati pertinenti per i sottoscrittori.
Imparare a usare gli eventi in Drupal 8 ti aiuterà a comprendere meglio lo sviluppo con moduli personalizzati e ti preparerà al futuro in cui (si spera) gli eventi sostituiranno gli hook. Quindi, creiamo un modulo personalizzato che mostri come usare ognuno di questi componenti in Drupal 8.
Il mio primo sottoscrittore di eventi in Drupal 8
Creiamo il nostro primo sottoscrittore di eventi in Drupal 8, utilizzando alcuni eventi di base. Creeremo un sottoscrittore che mostra un messaggio all’utente quando un oggetto Config viene salvato o eliminato.
La prima cosa che ci serve è un modulo in cui lavorare. L’ho chiamato custom_events.
name: Custom Events type: module description: Custom/Example event work. core: 8.x package: Custom
Il passo successivo è registrare un nuovo sottoscrittore. Per farlo creiamo custom_events.services.yml. Se provieni da Drupal 7 e sei più abituato al sistema di hook, pensa a questo passaggio come alla scrittura di una funzione "hook_my_event_name" nel tuo modulo o tema.
services:
my_config_events_subscriber:
class: '\Drupal\custom_events\EventSubscriber\ConfigEventsSubscriber'
tags:
- { name: 'event_subscriber' }
Abbastanza semplice: definiamo un nuovo servizio, specifichiamo la classe e lo contrassegniamo come “event_subscriber”.
Ora scriviamo la classe del sottoscrittore. Questa deve:
1. Implementare EventSubscriberInterface.
2. Avere un metodo getSubscribedEvents() che restituisce un array. Le chiavi sono i nomi degli eventi a cui vogliamo iscriverci, i valori i metodi da eseguire.
Ecco la nostra classe sottoscrittore che ascolta gli eventi di ConfigEvents:
class ConfigEventsSubscriber implements EventSubscriberInterface {
public static function getSubscribedEvents() {
return [
ConfigEvents::SAVE => 'configSave',
ConfigEvents::DELETE => 'configDelete',
];
}
public function configSave(ConfigCrudEvent $event) {
$config = $event->getConfig();
\Drupal::messenger()->addStatus('Saved config: ' . $config->getName());
}
public function configDelete(ConfigCrudEvent $event) {
$config = $event->getConfig();
\Drupal::messenger()->addStatus('Deleted config: ' . $config->getName());
}
}
Abbiamo implementato EventSubscriberInterface, creato getSubscribedEvents() e gestito gli eventi SAVE e DELETE mostrando messaggi.
Il mio primo evento in Drupal 8 e la sua distribuzione
Ora creiamo un nuovo evento. Ad esempio per “hook_user_login”, non presente come evento nel core. Creiamo una classe UserLoginEvent che estende Event:
class UserLoginEvent extends Event {
const EVENT_NAME = 'custom_events_user_login';
public $account;
public function __construct(UserInterface $account) {
$this->account = $account;
}
}
Quindi, nel file custom_events.module, inviamo l’evento durante hook_user_login:
function custom_events_user_login($account) {
$event = new UserLoginEvent($account);
$dispatcher = \Drupal::service('event_dispatcher');
$dispatcher->dispatch(UserLoginEvent::EVENT_NAME, $event);
}
Così inviamo il nuovo evento personalizzato. Ora scriviamo un sottoscrittore UserLoginSubscriber per ascoltarlo e mostrare un messaggio di benvenuto.
Priorità dei sottoscrittori di eventi
Un altro vantaggio del sistema degli eventi è che i sottoscrittori possono impostare la propria priorità direttamente, invece di modificare il peso del modulo o usare hook aggiuntivi.
Per farlo, in getSubscribedEvents() invece di restituire solo il nome del metodo, restituiamo un array con il metodo e la priorità:
public static function getSubscribedEvents() {
return [
ConfigEvents::SAVE => ['configSave', 100],
ConfigEvents::DELETE => ['configDelete', -100],
];
}
In questo modo, possiamo controllare l’ordine di esecuzione dei listener per lo stesso evento.
Riferimenti:
- Repository GitHub – contiene tutto il codice mostrato.
- Documentazione Symfony: Event Subscribers
- Documentazione Symfony: Event Dispatcher
- Post originale sul blog – molto simile a questa guida, con ulteriori dettagli sul futuro degli eventi in Drupal.