In the last lesson you subscribed to events Drupal core already fires. Now flip that around: what if your own module needs to announce that something happened, so other modules can react without you ever having to know they exist? That's exactly what a custom event is for. This lesson defines the event itself — the next two lessons will dispatch it and react to it.
What you'll learn in this lesson
- Why a dedicated event class (not just a string name) is the right way to define an event
- How to carry contextual data along with an event using constructor properties
- The convention of collecting event name constants in a single documentation class
- Why this pattern is Drupal's implementation of the classic Observer design pattern
Think of it like a notice board, not a phone call
A hook is like calling every module on the phone directly: "hey, did you want to do something about this form?" A custom event is more like pinning a notice to a public board: "Incident reported: type X." You have no idea who's reading the board, and you don't need to — any module, including ones written after yours, can walk up, read the notice, and act on it. That decoupling is the entire point: your module doesn't hold a list of "modules to notify," it just posts to the board and moves on.
Step 1: name the event with a documented constant
<?php
namespace Drupal\my_module\Event;
/**
* Defines events for the my_module module.
*
* @see \Drupal\my_module\Event\IncidentReportEvent
*/
final class IncidentEvents {
/**
* Name of the event fired when a new incident is reported.
*
* The event listener method receives a
* \Drupal\my_module\Event\IncidentReportEvent instance.
*
* @Event
*/
const NEW_REPORT = 'my_module.new_incident_report';
}
This class does nothing at runtime — it exists purely as a documented, typo-proof place to hold the event's string name. Declaring it final signals it should never be extended; it's a name registry, not a base class. If your module fires several related events, add more constants to this same class rather than scattering event name strings through your codebase.
Step 2: define the event object itself
<?php
namespace Drupal\my_module\Event;
use Symfony\Contracts\EventDispatcher\Event;
/**
* Wraps an incident report event for event subscribers.
*/
class IncidentReportEvent extends Event {
protected $type;
protected $report;
public function __construct($type, $report) {
$this->type = $type;
$this->report = $report;
}
public function getType() {
return $this->type;
}
public function getReport() {
return $this->report;
}
}
Extending Symfony's base Event class is what makes this dispatchable through Drupal's event dispatcher service. Whatever data a subscriber will need to react intelligently — here, the incident's $type and free-text $report — becomes a constructor argument and a getter. Anything you don't put on the event object, a subscriber simply can't see, so think about this step from the subscriber's point of view: what will it need to know to decide what to do?
IncidentReportEvent $event in its method signature, so your IDE and static analysis tools know exactly what's available — getType(), getReport() — instead of hoping an array key exists and spells its name correctly.Quick check: if a future version of this event needed to carry an extra piece of data — say, the reporting user's ID — where would that change go? (Answer: add a new property, constructor parameter, and getter to the
IncidentReportEventclass itself. TheIncidentEventsclass never changes for this — it only ever holds the event's name, never its payload.)
Key takeaways
- A custom event needs two pieces: a documented name constant (in a class like
IncidentEvents) and an event object class extending Symfony'sEventthat carries the relevant data. - Keep the name-constant class
final— it's a registry, not something to extend. - Whatever a subscriber will need to react correctly belongs on the event object as a constructor property with a getter — nothing more, nothing less.
- This is Drupal's implementation of the Observer pattern: the module firing the event never needs to know who, if anyone, is listening.
Coming up next
Defining the event class is only half the story — next you'll actually dispatch it from a real form submission, using the event_dispatcher service.