logo

Extra Block Types (EBT) - Neue Erfahrung im Layout Builder❗

Extra Block Types (EBT) - gestylte, anpassbare Blocktypen: Diashows, Registerkarten, Karten, Akkordeons und viele andere. Eingebaute Einstellungen für Hintergrund, DOM Box, Javascript Plugins. Erleben Sie die Zukunft der Layouterstellung schon heute.

Demo EBT-Module EBT-Module herunterladen

❗Extra Absatztypen (EPT) - Erfahrung mit neuen Absätzen

Extra Paragraph Types (EPT) - analoger, auf Absätzen basierender Satz von Modulen.

Demo EPT-Module EPT-Module herunterladen

Scroll

Event Subscriber und Event Dispatcher. Das Event-System in Drupal.

20/06/2025, by Ivan

Menu

Überblick über Event-Systeme

Event-Systeme werden in vielen komplexen Anwendungen verwendet, um Erweiterungen die Möglichkeit zu geben, das Verhalten des Systems zu ändern. Ein Event-System kann auf verschiedene Weise implementiert werden, aber im Allgemeinen sind die Konzepte und Komponenten, die das System ausmachen, ähnlich.

  • Event Subscribers (Ereignis-Abonnenten) – manchmal auch „Listener“ genannt – sind aufrufbare Methoden oder Funktionen, die auf ein im gesamten Event-Registry verbreitetes Ereignis reagieren.
  • Event Registry (Ereignis-Registrierung) – der Ort, an dem Event-Abonnenten gesammelt und sortiert werden.
  • Event Dispatcher (Ereignis-Dispatcher) – der Mechanismus, mit dem ein Ereignis initiiert oder im System „versendet“ wird.
  • Event Context (Ereignis-Kontext) – viele Events benötigen einen bestimmten Satz an Daten, der für die Event-Abonnenten wichtig ist. Dies kann ein einfacher Wert sein, der an den Subscriber übergeben wird, oder komplexer, z.B. eine speziell erstellte Klasse, die relevante Daten enthält.

Drupal Hooks

Während eines Großteils seiner Geschichte nutzte Drupal ein rudimentäres Event-System über sogenannte „Hooks“. Schauen wir uns an, wie sich das Konzept „Hooks“ auf diese 4 Event-System-Komponenten aufteilt.

  • Event Subscribers – Drupal-Hooks werden registriert, indem eine Funktion mit einem bestimmten Namen definiert wird. Möchten Sie z.B. auf das vorbereitete Ereignis „hook_my_event_name“ hören, definieren Sie eine neue Funktion mit dem Namen myprefix_my_event_name(), wobei „myprefix“ der Name Ihres Moduls oder Themes ist.
  • Event Registry – Drupal-Hooks werden im Cache-Bucket „cache_bootstrap“ unter der ID „module_implements“ gespeichert. Das ist einfach ein Array von Modulen, die die Funktion mit dem jeweiligen Hook-Namen implementieren.
  • Event Dispatcher – Hooks werden in Drupal 7 und Drupal 8 unterschiedlich ausgelöst:
    1. Drupal 7: Hooks werden über die Funktion module_invoke_all() ausgelöst.
    2. Drupal 8: Hooks werden über die Service-Methode \Drupal::moduleHandler()->invokeAll() ausgelöst.
  • Event Context – der Kontext wird dem Subscriber über Parameter übergeben. Beispiel: Diese Dispatcher-Methoden führen alle Implementierungen von „hook_my_event_name“ aus und übergeben den Parameter $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]);

Einige Nachteile des Hook-Ansatzes für Events:

  • Events werden nur beim Cache-Aufbau registriert.

Drupal sucht neue Hooks nur beim Erstellen bestimmter Caches. Das heißt, wenn Sie einen neuen Hook implementieren wollen, müssen Sie die Caches je nach Hook neu bauen.

  • Pro Modul kann nur einmal auf jedes Event reagiert werden.

Da Events durch speziell benannte Funktionen implementiert werden, darf pro Modul oder Theme nur eine Implementierung eines Events existieren. Das ist eine künstliche Einschränkung gegenüber anderen Event-Systemen.

  • Die Reihenfolge der Event-Ausführung lässt sich nicht einfach steuern.

Drupal bestimmt die Reihenfolge der Event-Subscriber über das Modulgewicht in der Systemhierarchie. Module und Themes haben „Gewichte“, die bestimmen, in welcher Reihenfolge sie geladen werden und somit die Reihenfolge, in der Events abgearbeitet werden. Um dieses Problem zu umgehen, wurde in Drupal 7 später der Hook „hook_module_implements_alter“ eingeführt, mit dem man die Reihenfolge der Hook-Ausführung ändern kann, ohne das Modulgewicht zu ändern.

Mit der Basis auf Symfony in Drupal 8 existiert nun ein anderes Event-System. Dieses ist in den meisten Fällen ein besseres Event-System. Obwohl Drupal 8 Core nicht viele Events auslöst, nutzen viele Module dieses System bereits.

Drupal 8 Events

Drupal 8 Events ähneln stark denen von Symfony. Schauen wir, wie sich das auf die Komponenten des Event-Systems aufteilt.

  • Event Subscribers – Klassen, die \Symfony\Component\EventDispatcher\EventSubscriberInterface implementieren.
  • Event Dispatcher – Klassen, die \Symfony\Component\EventDispatcher\EventDispatcherInterface implementieren. Meistens gibt es mindestens eine Dispatcher-Instanz als Service im System, aber weitere können erstellt werden.
  • Event Registry – Der Registry der Subscriber wird im Event Dispatcher als Array mit Event-Namen und Priorität (Reihenfolge) gehalten. Wenn ein Event als Service registriert wird, wird es im global verfügbaren Dispatcher registriert.
  • Event Context – Klassen, die \Symfony\Component\EventDispatcher\Event erweitern. Meist erstellt jede Erweiterung für ihr eigenes Event eine eigene Event-Klasse, die die benötigten Daten für die Subscriber enthält.

Das Lernen, wie man Drupal 8 Events verwendet, hilft Ihnen, die Entwicklung mit benutzerdefinierten Modulen besser zu verstehen und bereitet Sie auf die Zukunft vor, in der Events hoffentlich die Hooks ablösen. Also, erstellen wir ein benutzerdefiniertes Modul, das zeigt, wie man diese Event-Komponenten in Drupal 8 verwendet.

Mein erster Drupal 8 Event Subscriber

Erstellen wir unseren ersten Event Subscriber in Drupal 8 mit einigen Basis-Events. Ich mag es, mit etwas Einfachem zu starten, also machen wir einen Subscriber, der eine Nachricht anzeigt, wenn ein Config-Objekt gespeichert oder gelöscht wird.

Zuerst brauchen wir ein Modul, in dem wir arbeiten. Ich nenne es custom_events.

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

Als nächstes registrieren wir unseren neuen Event Subscriber in Drupal. Dafür erstellen wir custom_events.services.yml. Wenn Sie von Drupal 7 kommen und die Hook-Systematik kennen, können Sie diesen Schritt mit dem Schreiben einer Funktion "hook_my_event_name" vergleichen.

services:
  # Name dieses Services.
  my_config_events_subscriber:
    # Event Subscriber Klasse, die auf Events hört.
    class: '\Drupal\custom_events\EventSubscriber\ConfigEventsSubscriber'
    # Markierung als event_subscriber, um den Subscriber im event_dispatch Service zu registrieren.
    tags:
      - { name: 'event_subscriber' }

Das ist einfach, aber lassen Sie uns das kurz aufschlüsseln:

1) Wir definieren einen neuen Service namens "my_config_events_subscriber".
2) Wir setzen die Eigenschaft "class" auf den globalen Klassennamen der neuen PHP-Klasse, die wir schreiben werden.
3) Wir definieren das Property „tags“ und fügen das Tag „event_subscriber“ hinzu. So wird der Service als Event Subscriber registriert.

Alternativ können Sie den PHP-Klassennamen des Event Subscribers (ohne führenden Backslash) als Servicenamen verwenden und die Eigenschaft „class“ weglassen, z.B.:

services:
  # Name des Services, hier der Event Subscriber Klasse.
  Drupal\custom_events\EventSubscriber\ConfigEventsSubscriber:
    tags:
      - { name: 'event_subscriber' }

Jetzt schreiben wir die Subscriber-Klasse. Diese muss zwei Anforderungen erfüllen:

1. Sie muss das Interface EventSubscriberInterface implementieren.
2. Sie muss die Methode getSubscribedEvents() haben, die ein Array zurückgibt. Die Schlüssel sind die Event-Namen, auf die Sie hören wollen, die Werte sind die Methoden, die aufgerufen werden.

Hier unser Subscriber, der Events aus der Klasse ConfigEvents abonniert und für jedes Event eine lokale Methode aufruft.

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;

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

  /**
   * {@inheritdoc}
   *
   * @return array
   *   Die Events, auf die gehört wird, und die Methoden, die ausgeführt werden.
   */
  public static function getSubscribedEvents() {
    return [
      ConfigEvents::SAVE => 'configSave',
      ConfigEvents::DELETE => 'configDelete',
    ];
  }

  /**
   * Reaktion auf das Speichern eines Config-Objekts.
   *
   * @param \Drupal\Core\Config\ConfigCrudEvent $event
   *   Config Crud Event.
   */
  public function configSave(ConfigCrudEvent $event) {
    $config = $event->getConfig();
    \Drupal::messenger()->addStatus('Konfiguration gespeichert: ' . $config->getName());
  }

  /**
   * Reaktion auf das Löschen eines Config-Objekts.
   *
   * @param \Drupal\Core\Config\ConfigCrudEvent $event
   *   Config Crud Event.
   */
  public function configDelete(ConfigCrudEvent $event) {
    $config = $event->getConfig();
    \Drupal::messenger()->addStatus('Konfiguration gelöscht: ' . $config->getName());
  }

}

Das war’s! Es sieht einfach aus, aber beachten Sie Folgendes:

  • Wir implementieren das Interface EventSubscriberInterface.
  • Wir implementieren getSubscribedEvents(), das ein Array mit Eventnamen und Methoden zurückgibt.
  • In beiden Methoden erwarten wir ein ConfigCrudEvent-Objekt, das die Methode getConfig() besitzt, die das Config-Objekt zurückgibt.

Einige Fragen, die auftauchen könnten:

  • Was ist ConfigEvents::SAVE und woher kommt es?

Beim Definieren von Events erstellt man meist eine global verfügbare Konstante mit dem Event-Namen. Hier hat \Drupal\Core\Config\ConfigEvents die Konstante SAVE mit dem Wert „config.save“.

  • Warum erwarten wir ein ConfigCrudEvent-Objekt und wie wissen wir das?

Beim Definieren eigener Events ist es üblich, eine eigene Event-Klasse zu erstellen, die die nötigen Daten kapselt und eine einfache API bereitstellt. Hier haben wir das am besten durch Studium des Quellcodes und der Dokumentation ermittelt.

Nun können wir das Modul aktivieren und das Event testen. Wir erwarten, dass immer, wenn ein Config-Objekt in Drupal gespeichert oder gelöscht wird, eine Nachricht mit dem Config-Namen erscheint.

Config-Objekte sind in Drupal 8 weit verbreitet. Die meisten Module verwalten ihre Einstellungen darüber, also können wir einfach das Modul installieren und deinstallieren und beobachten, welche Config-Objekte gespeichert und gelöscht werden.

1. Aktivieren Sie das Modul „custom_events“.
2. Installieren Sie das Modul „Statistik“.

events-1-installed

Nachricht nach der Installation des Statistik-Moduls.

Es sieht so aus, als wurden zwei Config-Objekte gespeichert: core.extension (verwalten installierte Module und Themes) und statistics.settings.

3. Deinstallieren Sie das Modul „Statistik“.

events-2-uninstalled

Nachricht nach der Deinstallation des Statistik-Moduls.

Diesmal sehen wir sowohl SAVE als auch DELETE Events: statistics.settings wurde gelöscht, core.extension wurde gespeichert.

Ich würde sagen, das ist ein Erfolg! Wir haben erfolgreich zwei Kern-Events in Drupal abonniert.

Als Nächstes schauen wir uns an, wie man eigene Events erstellt und diese an andere Module sendet.

Mein erstes Drupal 8 Event und Event Dispatching

Zuerst müssen wir entscheiden, welche Art von Event wir senden wollen und wann. Wir erstellen ein Event für den Drupal-Hook, der im Core noch kein Event hat: „hook_user_login“.

Wir erstellen eine neue Klasse, die Event erweitert, und nennen sie UserLoginEvent. Außerdem definieren wir einen global verfügbaren Event-Namen für Subscriber.

src/Event/UserLoginEvent.php
<?php

namespace Drupal\custom_events\Event;

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

/**
 * Event, das ausgelöst wird, wenn ein Nutzer sich einloggt.
 */
class UserLoginEvent extends Event {

  const EVENT_NAME = 'custom_events_user_login';

  /**
   * Das Benutzerkonto.
   *
   * @var \Drupal\user\UserInterface
   */
  public $account;

  /**
   * Konstruktor.
   *
   * @param \Drupal\user\UserInterface $account
   *   Das eingeloggte Benutzerkonto.
   */
  public function __construct(UserInterface $account) {
    $this->account = $account;
  }

}
  • UserLoginEvent::EVENT_NAME ist eine Konstante mit dem Wert „custom_events_user_login“. Das ist der Name unseres neuen benutzerdefinierten Events.
  • Der Konstruktor erwartet ein UserInterface-Objekt und speichert es als Eigenschaft. So ist das $account-Objekt für Subscriber zugänglich.

Das war’s!

Jetzt müssen wir das Event nur noch dispatchen. Das machen wir im Hook „hook_user_login“. Erstellen Sie die Datei custom_events.module.

<?php

/**
 * @file
 * Enthält custom_events.module.
 */

use Drupal\custom_events\Event\UserLoginEvent;

/**
 * Implements hook_user_login().
 */
function custom_events_user_login($account) {
  // Event instanziieren.
  $event = new UserLoginEvent($account);

  // Event Dispatcher Service holen und Event dispatchen.
  $event_dispatcher = \Drupal::service('event_dispatcher');
  $event_dispatcher->dispatch(UserLoginEvent::EVENT_NAME, $event);
}

Innerhalb unserer Implementierung von „hook_user_login“ tun wir Folgendes, um unser neues Event zu senden:

1. Erstellen ein neues UserLoginEvent-Objekt und übergeben den $account aus dem Hook-Kontext.
2. Holen den Service „event_dispatcher“.
3. Rufen die Methode dispatch() auf, geben den Event-Namen und das gerade erstellte Event-Objekt weiter.

Das war’s! Nun senden wir unser benutzerdefiniertes Event, wenn sich ein Nutzer einloggt.

Als nächstes vervollständigen wir unser Beispiel, indem wir einen Subscriber für unser neues Event erstellen. Zuerst aktualisieren wir die services.yml und fügen unseren Subscriber hinzu.

services:
  # Name dieses Services.
  my_config_events_subscriber:
    class: '\Drupal\custom_events\EventSubscriber\ConfigEventsSubscriber'
    tags:
      - { name: 'event_subscriber' }

  # Subscriber für das Event, das wir in hook_user_login senden.
  custom_events_user_login:
    class: '\Drupal\custom_events\EventSubscriber\UserLoginSubscriber'
    tags:
      - { name: 'event_subscriber' }

Wie zuvor definieren wir einen neuen Service und markieren ihn als event_subscriber. Nun schreiben wir die Klasse UserLoginSubscriber.

src/EventSubscriber/UserLoginSubscriber.php
<?php

namespace Drupal\custom_events\EventSubscriber;

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

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

  /**
   * Datenbankverbindung.
   *
   * @var \Drupal\Core\Database\Connection
   */
  protected $database;

  /**
   * Datumsformatierer.
   *
   * @var \Drupal\Core\Datetime\DateFormatterInterface
   */
  protected $dateFormatter;

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents() {
    return [
      // Statische Klassen-Konstante => Methode dieser Klasse.
      UserLoginEvent::EVENT_NAME => 'onUserLogin',
    ];
  }

  /**
   * Reaktion auf das User Login Event.
   *
   * @param \Drupal\custom_events\Event\UserLoginEvent $event
   *   Das Event-Objekt.
   */
  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('Willkommen, Ihr Konto wurde am %created_date erstellt.', [
      '%created_date' => $dateFormatter->format($account_created, 'short'),
    ]));
  }

}

Erklärung:

1. Wir abonnieren das Event mit dem Namen UserLoginEvent::EVENT_NAME über die Methode onUserLogin() (der Name der Methode, die wir erstellt haben).
2. Im onUserLogin greifen wir auf die Eigenschaft $account (den gerade eingeloggenen Benutzer) des Event-Objekts zu und machen etwas damit.
3. Wenn sich ein Benutzer einloggt, sieht er eine Nachricht mit dem Datum seiner Kontoerstellung.

events-3-login

Nachricht nach dem Login.

Voila! Wir haben sowohl ein neues Custom Event gesendet als auch erfolgreich darauf reagiert.

Prioritäten der Event Subscribers

Ein weiteres großartiges Feature des Event-Systems ist, dass Subscriber ihre eigene Priorität festlegen können, ohne das Gewicht des gesamten Moduls zu ändern oder einen weiteren Hook zum Ändern der Priorität zu verwenden (wie bei Hooks).

Das ist einfach, aber um es gut zu demonstrieren, schreiben wir einen weiteren Subscriber, da wir bereits einen haben. Schreiben wir „AnotherConfigEventSubscriber“ und setzen Prioritäten für seine Listener.

Zuerst registrieren wir den neuen Subscriber in services.yml:

services:
  # Name dieses 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' }

Dann schreiben wir AnotherConfigEventSubscriber.php:

<?php

namespace Drupal\custom_events\EventSubscriber;

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

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

  /**
   * {@inheritdoc}
   *
   * @return array
   *   Die Events, auf die gehört wird, und die Methoden, die ausgeführt werden.
   */
  public static function getSubscribedEvents() {
    return [
      ConfigEvents::SAVE => ['configSave', 100],
      ConfigEvents::DELETE => ['configDelete', -100],
    ];
  }

  /**
   * Reaktion auf das Speichern eines Config-Objekts.
   *
   * @param \Drupal\Core\Config\ConfigCrudEvent $event
   *   Config Crud Event.
   */
  public function configSave(ConfigCrudEvent $event) {
    $config = $event->getConfig();
    \Drupal::messenger()->addStatus('(Another) Konfiguration gespeichert: ' . $config->getName());
  }

  /**
   * Reaktion auf das Löschen eines Config-Objekts.
   *
   * @param \Drupal\Core\Config\ConfigCrudEvent $event
   *   Config Crud Event.
   */
  public function configDelete(ConfigCrudEvent $event) {
    $config = $event->getConfig();
    \Drupal::messenger()->addStatus('(Another) Konfiguration gelöscht: ' . $config->getName());
  }

}

Der wichtigste Unterschied ist, dass wir in getSubscribedEvents() nicht nur die Methode angeben, sondern ein Array, in dem der erste Wert der Methodenname und der zweite der Prioritätswert ist.

Also ändern wir von:

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

zu:

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

Das erwarten wir als Ergebnis:

  • AnotherConfigEventSubscriber::configSave() hat eine sehr hohe Priorität und wird vor ConfigEventSubscriber::configSave() ausgeführt.
  • AnotherConfigEventSubscriber::configDelete() hat eine sehr niedrige Priorität und wird nach ConfigEventSubscriber::configDelete() ausgeführt.

Sehen wir uns das SAVE-Event an, indem wir das Statistik-Modul erneut aktivieren.

events-4-installed-priorities

Installation des Statistik-Moduls und Anzeige der Nachrichten.

Super! Unser neuer Listener auf ConfigEvents::SAVE wurde vor dem ersten ausgeführt. Jetzt deinstallieren wir Statistik und schauen uns das DELETE-Event an.

events-5-uninstalled-priorities

Deinstallation des Statistik-Moduls und Anzeige der Nachrichten.

Auch hier super! Unser neuer Listener auf ConfigEvents::DELETE wurde nach dem ersten ausgeführt, da er eine sehr niedrige Priorität hat.

Hinweis: Wenn bei der Registrierung eines Event Subscribers keine Priorität angegeben wird, ist der Standardwert 0.

Links:

Drupal’s online documentation is © 2000-2020 by the individual contributors and can be used in accordance with the Creative Commons License, Attribution-ShareAlike 2.0. PHP code is distributed under the GNU General Public License.