Building an Event Subscriber Service in Drupalfor Drupal 11 , 10 , 9 , and 8

Last updated :  

You've defined the event and dispatched it — now build the piece that actually does something when it fires. An event subscriber is a plain service, tagged so Drupal knows to ask it which events it cares about, that runs one of its methods whenever a matching event is dispatched anywhere in the system.

What you'll learn in this lesson

  • The two things a subscriber needs: implementing EventSubscriberInterface and a services.yml tag
  • How getSubscribedEvents() maps an event name to one or more handler methods
  • How to read data off the event object inside a handler method
  • What $event->stopPropagation() does, and when to reach for it

Registering the subscriber service

services:
  my_module.incident_subscriber:
    class: Drupal\my_module\EventSubscriber\IncidentSubscriber
    tags:
      - { name: event_subscriber }

Same tag you saw for core kernel events a few lessons back — event_subscriber works identically whether you're listening to Drupal core or to your own custom event. This is one of the nice consistencies of the system: once you know how to subscribe to one kind of event, you know how to subscribe to all of them.

The subscriber class

<?php

namespace Drupal\my_module\EventSubscriber;

use Drupal\my_module\Event\IncidentEvents;
use Drupal\my_module\Event\IncidentReportEvent;
use Drupal\Core\Messenger\MessengerTrait;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class IncidentSubscriber implements EventSubscriberInterface {

  use StringTranslationTrait;
  use MessengerTrait;

  public static function getSubscribedEvents(): array {
    $events[IncidentEvents::NEW_REPORT][] = ['notifyMario'];
    $events[IncidentEvents::NEW_REPORT][] = ['notifyBatman', -100];
    $events[IncidentEvents::NEW_REPORT][] = ['notifyDefault', -255];
    return $events;
  }

  public function notifyMario(IncidentReportEvent $event) {
    if ($event->getType() == 'stolen_princess') {
      $this->messenger()->addStatus($this->t('Mario has been alerted.'));
      $event->stopPropagation();
    }
  }

  public function notifyBatman(IncidentReportEvent $event) {
    if ($event->getType() == 'joker') {
      $this->messenger()->addStatus($this->t('Batman has been alerted.'));
      $event->stopPropagation();
    }
  }

  public function notifyDefault(IncidentReportEvent $event) {
    $this->messenger()->addStatus($this->t('Thank you for reporting this incident.'));
    $event->stopPropagation();
  }

}

getSubscribedEvents() — a map, not a hook

This static method is the entire contract of EventSubscriberInterface. It returns an array keyed by event name; each key maps to one or more [method_name, priority] pairs (priority is optional and defaults to 0). A single subscriber class can react to any number of different events by adding more keys — you don't need a separate class per event, only per logical grouping of related reactions.

Reading the event, and stopping propagation

Each handler method receives the exact event object that was dispatched — here, an IncidentReportEvent — and can call its getters (getType(), getReport()) to decide how to react. $event->stopPropagation() tells the dispatcher "no further subscribers for this event should run" — useful when one subscriber's reaction makes any later ones redundant or actively wrong, but be careful with it: stopping propagation means any OTHER unrelated module's subscriber to the same event silently never runs either.

Three methods, one class, one event name. Notice all three handler methods above subscribe to the exact same IncidentEvents::NEW_REPORT event, each checking a different condition and stopping propagation once one matches. This "chain of increasingly generic handlers, ending in a catch-all" pattern — Mario, then Batman, then a default fallback — is worth recognizing; you'll see the same shape in Drupal core's own access-checking subscribers.

Quick check: if you reported a "Cat stuck in tree" incident (type cat), which of the three methods above actually shows a message — and why? (Answer: only notifyDefault(). notifyMario() checks for stolen_princess and notifyBatman() checks for joker — neither matches, so neither stops propagation, letting the event fall through to the priority -255 catch-all.)

Key takeaways

  • A subscriber is a plain service implementing EventSubscriberInterface, tagged event_subscriber in services.yml — the same tag whether the event is core or custom.
  • getSubscribedEvents() maps event names to [method, priority] pairs; one class can handle multiple events or multiple conditions on the same event.
  • Handler methods receive the exact event object that was dispatched and can call its getters to inspect the data it carries.
  • $event->stopPropagation() halts remaining subscribers for that event entirely — including ones from other, unrelated modules — so use it deliberately.

Coming up next

What happens when two subscribers from different modules both want to react to the same event, and the order matters? Next, you'll control exactly that with subscriber priorities.