milpa/events

String-named event dispatch for the Milpa PHP framework: dot-segment wildcard subscriptions, priorities, listener error isolation, and a pluggable async (queue) seam.

Maintainers

Package info

github.com/getmilpa/events

pkg:composer/milpa/events

Transparency log

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.1.0 2026-07-07 18:28 UTC

This package is auto-updated.

Last update: 2026-07-07 19:45:49 UTC


README

Milpa

Milpa Events

The reference event dispatcher for the Milpa PHP framework, built on milpa/core. String-named events with dot-segment wildcard subscriptions (user.*), priority ordering, per-listener error isolation, and a pluggable async (queue) seam — the concrete implementation of the MilpaEventDispatcherInterface contract milpa/core defines.

CI Packagist PHP License Docs

milpa/events is where milpa/core's event-dispatch seam becomes a working engine. Milpa\Interfaces\Event\MilpaEventDispatcherInterface is a contract defined in core; this package is the concrete EventDispatcher — plugins publish and subscribe to string-named events ('user.created', 'order.shipped') without depending on each other directly. No Doctrine, no HTTP kernel, no concrete queue — the async seam is a plain callable you wire in; the queue itself lives in your host application.

Install

composer require milpa/events

Quick example

Subscribe by exact name or by a dot-segment wildcard, dispatch, and let priority decide the call order — higher priority runs first:

use Milpa\Eventing\EventDispatcher;
use Psr\Log\NullLogger;

$dispatcher = new EventDispatcher(new NullLogger());

$dispatcher->subscribe('user.*', function (string $event, array $payload): void {
    // catches every one-segment event under `user.` — user.created, user.updated, ...
});

$dispatcher->subscribe('user.created', function (string $event, array $payload): void {
    // runs before the wildcard handler above: higher priority
}, priority: 10);

$dispatcher->dispatch('user.created', ['id' => 42]);

$dispatcher->hasSubscribers('user.created'); // true
$dispatcher->hasSubscribers('order.created'); // false — no exact or wildcard match

A handler that throws is logged and does not stop the remaining handlers — one bad listener never aborts a dispatch:

$dispatcher->subscribe('order.placed', fn () => throw new \RuntimeException('boom'));
$dispatcher->subscribe('order.placed', fn () => /* still runs */ null);

$dispatcher->dispatch('order.placed'); // both handlers ran; the exception was logged, not thrown

Wildcard grammar

Event names are dot-separated segments; * matches exactly one segment — it never spans a .. Matching is case-sensitive and anchored (the whole name must match):

Pattern Matches Does not match
user.* user.created, user.deleted user.profile.updated (two segments after user.)
*.created user.created, order.created user.createdX (anchored, not a substring match)
* boot, ready (single-segment names) user.created (dotted)

Async: a seam, not an implementation

dispatch($event, $payload, async: true) requests deferred execution. This package ships no queue — you wire one in with setAsyncDispatcher():

$dispatcher->setAsyncDispatcher(function (string $event, array $payload): void {
    // hand off to your queue (Symfony Messenger, a Doctrine-backed job table, ...)
});

$dispatcher->dispatch('order.placed', ['id' => 1], async: true); // -> queue, not inline

Without a dispatcher wired, async: true degrades to synchronous dispatch (subscribers run inline, in the same call) rather than silently dropping the event — a conformant fallback per the interface's documented $async contract, not a bug. Both branches are covered in tests/EventDispatcherTest.php.

Why the namespace is Milpa\Eventing

milpa/core already owns Milpa\Events\ for its event objects (VerificationRequestedEvent and friends, under src/Events/). A package literally named milpa/events colliding with that namespace would either clash or force an awkward split. Milpa\EventDispatcher\EventDispatcher was the other candidate and stutters. Milpa\Eventing avoids both: no collision, no stutter, and it names the mechanism (dispatch, subscribe, wildcards, priority) distinctly from Milpa\Events, which names the event objects — the two packages' responsibilities stay visibly distinct. This follows the family-wide rule: namespace semantically correct beats cosmetic symmetry with the package name.

What lives where

Layer Package Owns
Contracts milpa/core MilpaEventDispatcherInterface, EventSubscriberInterface — the seam, not the engine.
Dispatcher milpa/events (this package) The concrete EventDispatcher: exact + wildcard subscriber matching, priority ordering, listener error isolation, and the async seam.
Your app your host / plugins The queue setAsyncDispatcher() hands events to, and any PSR-3 logger you pass in.

Requirements

Documentation

Full API reference: getmilpa.github.io/events — generated straight from the source DocBlocks and dressed with the Milpa design system.

Contributing

Contributions are welcome — see CONTRIBUTING.md. Please report security issues via SECURITY.md, and note that this project follows a Code of Conduct.

License

Apache-2.0 © TeamX Agency.

Milpa is designed, built, and maintained by TeamX Agency.