Events — EventDispatcher

Events — EventDispatcher

PSR-14 dispatcher met prioriteits-based listeners. TableRepository en DynamicWriter dispatchen lifecycle-events (Before/After Insert/Update/Delete) wanneer je een EventDispatcher injecteert. Listeners op BeforeInsertEvent en BeforeUpdateEvent mogen via setData() de data muteren.

Basics — listener registreren en dispatchen

on($eventClass, $callable, priority) registreert. Hogere priority = eerst.

$provider   = new ListenerProvider();
$dispatcher = new EventDispatcher($provider);

$log = [];
$provider->on(BeforeInsertEvent::class, function (BeforeInsertEvent $e) use (&$log) {
    $log[] = "BEFORE: {$e->table}";
});
$provider->on(AfterInsertEvent::class, function (AfterInsertEvent $e) use (&$log) {
    $log[] = "AFTER: {$e->table} id={$e->id}";
});

// Dispatch handmatig (normaal doet de repository dit voor je):
$dispatcher->dispatch(new BeforeInsertEvent('members', ['name' => 'Jan']));
$dispatcher->dispatch(new AfterInsertEvent('members', 42, ['name' => 'Jan']));

return $log;
Array
(
    [0] => BEFORE: members
    [1] => AFTER: members id=42
)

TableRepository — automatische event-dispatch

Inject de EventDispatcher in de repo-constructor; daarna komen alle insert/update/delete-events vanzelf langs.

$pdo->exec('CREATE TABLE members (id INTEGER PRIMARY KEY AUTOINCREMENT, email TEXT, name TEXT)');

$provider   = new ListenerProvider();
$dispatcher = new EventDispatcher($provider);

$log = [];
foreach ([BeforeInsertEvent::class, AfterInsertEvent::class,
          BeforeUpdateEvent::class, AfterUpdateEvent::class,
          AfterDeleteEvent::class] as $cls) {
    $provider->on($cls, function ($e) use (&$log, $cls) {
        $log[] = (new ReflectionClass($cls))->getShortName();
    });
}

$repo = new TableRepository($pdo, 'members', events: $dispatcher);
$id = $repo->insert(['email' => 'a@b', 'name' => 'A']);
$repo->update($id, ['name' => 'B']);
$repo->delete($id);

return $log;
Array
(
    [0] => BeforeInsertEvent
    [1] => AfterInsertEvent
    [2] => BeforeUpdateEvent
    [3] => AfterUpdateEvent
    [4] => AfterDeleteEvent
)

BeforeInsert / BeforeUpdate kan data muteren

setData() op het event verandert wat er naar de DB gaat — handig voor audit-velden, hashing, normalisatie.

$pdo->exec('CREATE TABLE members (id INTEGER PRIMARY KEY AUTOINCREMENT, email TEXT, name TEXT, created_at TEXT)');

$provider   = new ListenerProvider();
$dispatcher = new EventDispatcher($provider);

$provider->on(BeforeInsertEvent::class, function (BeforeInsertEvent $e) {
    $data = $e->getData();
    $data['name']       = strtoupper($data['name']);
    $data['created_at'] = date('Y-m-d H:i:s');
    $e->setData($data);
});

$repo = new TableRepository($pdo, 'members', events: $dispatcher);
$id   = $repo->insert(['email' => 'a@b', 'name' => 'jan']);

return $repo->find($id);    // → name=JAN, created_at gevuld
Array
(
    [id] => 1
    [email] => a@b
    [name] => JAN
    [created_at] => 2026-05-10 14:01:12
)

Priority — meerdere listeners voor hetzelfde event

Hogere priority loopt eerst. Default = 0.

$provider   = new ListenerProvider();
$dispatcher = new EventDispatcher($provider);

$log = [];
$provider->on(BeforeInsertEvent::class, function () use (&$log) { $log[] = 'low'; },    priority: 0);
$provider->on(BeforeInsertEvent::class, function () use (&$log) { $log[] = 'high'; },   priority: 10);
$provider->on(BeforeInsertEvent::class, function () use (&$log) { $log[] = 'medium'; }, priority: 5);

$dispatcher->dispatch(new BeforeInsertEvent('x', []));

return $log;   // ['high', 'medium', 'low']
Array
(
    [0] => high
    [1] => medium
    [2] => low
)

Eigen events maken

Een event is gewoon een class — geen interface verplicht (PSR-14 stoppable is optioneel via StoppableEventInterface).

final class UserRegistered {
    public function __construct(public readonly int $userId, public readonly string $email) {}
}

$provider->on(UserRegistered::class, function (UserRegistered $e) {
    // mail($e->email, ...);
});

$dispatcher->dispatch(new UserRegistered(42, 'jan@x.nl'));
UserRegistered event-class kan elk DTO zijn — geen interface vereist.

Via Kernel — `$kernel->events` en `$kernel->listeners`

Bij gebruik van de framework-Kernel zijn de dispatcher en listener-provider al gewired via de container.

// index.php / page-bootstrap
$kernel = require __DIR__ . '/bootstrap.php';

$kernel->listeners->on(AfterInsertEvent::class, function (AfterInsertEvent $e) {
    error_log("Insert: {$e->table} id={$e->id}");
});

// Pages mogen de dispatcher gebruiken voor eigen events:
$kernel->events->dispatch(new MyDomainEvent(...));
$kernel->events + $kernel->listeners hangen als virtual readonly properties op de Kernel.