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.
Requires
- php: >=8.3
- milpa/core: ^0.3
- psr/log: ^3
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.65
- phpstan/phpdoc-parser: ^2.3
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^11.5
This package is auto-updated.
Last update: 2026-07-07 19:45:49 UTC
README
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 theMilpaEventDispatcherInterfacecontractmilpa/coredefines.
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
- PHP ≥ 8.3
milpa/core^0.3psr/log^3
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.