waffle-commons / event-dispatcher
Event Dispatcher component for Waffle framework.
Package info
github.com/waffle-commons/event-dispatcher
pkg:composer/waffle-commons/event-dispatcher
Requires
- php: ^8.5
- psr/event-dispatcher: ^1.0
- waffle-commons/contracts: 0.1.0-beta2.1
Requires (Dev)
- carthage-software/mago: ^1.29
- cyclonedx/cyclonedx-php-composer: ^6.2
- php-mock/php-mock-phpunit: ^2.15
- phpunit/phpunit: ^12.5
- vimeo/psalm: ^6.16
This package is auto-updated.
Last update: 2026-05-30 19:46:35 UTC
README
Waffle Event Dispatcher Component
Release:
v0.1.0-beta2ย |ยCHANGELOG.mdPSR Compliance: PSR-14 (Psr\EventDispatcher\EventDispatcherInterface,ListenerProviderInterface,StoppableEventInterface)
A minimal, attribute-driven PSR-14 dispatcher. The dispatcher itself is final readonly and stateless; the listener provider stores the listener map and supports priority ordering and #[AsEventListener] attribute discovery.
๐ฆ Installation
composer require waffle-commons/event-dispatcher
๐งฑ Surface
| Class | Role |
|---|---|
Waffle\Commons\EventDispatcher\Dispatcher\EventDispatcher |
final readonly PSR-14 dispatcher. Walks listeners, respects StoppableEventInterface. |
Waffle\Commons\EventDispatcher\Provider\ListenerProvider |
Listener registry. Manual registration via addListener(), or attribute scanning via register($object). |
Waffle\Commons\EventDispatcher\Attribute\AsEventListener |
PHP 8 attribute marking a class or method as a listener. |
Waffle\Commons\EventDispatcher\Event\AbstractStoppableEvent |
Convenience base implementing StoppableEventInterface. |
๐ Manual registration
use Waffle\Commons\EventDispatcher\Dispatcher\EventDispatcher; use Waffle\Commons\EventDispatcher\Provider\ListenerProvider; $provider = new ListenerProvider(); $provider->addListener(UserRegistered::class, function (UserRegistered $event): void { // โฆ }, priority: 100); // higher priority = earlier $dispatcher = new EventDispatcher($provider); $event = $dispatcher->dispatch(new UserRegistered($userId));
๐ท๏ธ Attribute-driven registration (#[AsEventListener])
The attribute is declared as:
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)] final readonly class AsEventListener { public function __construct( public ?string $event = null, public int $priority = 0, ) {} }
Method-level โ event class resolved from the parameter type-hint
final class AuditListener { #[AsEventListener(priority: 50)] public function onUserRegistered(UserRegistered $event): void { // resolved automatically from the parameter type } } $provider->register(new AuditListener());
Class-level โ first public non-constructor method is the handler
#[AsEventListener(event: UserRegistered::class, priority: 50)] final class WelcomeMailer { public function send(UserRegistered $event): void { /* โฆ */ } } $provider->register(new WelcomeMailer());
๐ Stoppable events
use Waffle\Commons\EventDispatcher\Event\AbstractStoppableEvent; final class CancellableJob extends AbstractStoppableEvent { public function __construct(public readonly string $jobId) {} } $provider->addListener(CancellableJob::class, function (CancellableJob $e): void { if ($shouldCancel) { $e->stopPropagation(); } });
The dispatcher honours isPropagationStopped() and breaks out of the listener loop.
๐ PHP 8.5 features used
final readonly class EventDispatcherโ the dispatcher itself is immutable.- Constructor property promotion with explicit visibility on listeners.
- Typed properties + parameters throughout.
- Inheritance walks via native
get_parent_class()(not reflection caches), so listener resolution against parent event types isO(depth)without warm-up cost.
๐งญ Architectural boundary (mago guard)
An active dependency perimeter is enforced on every CI run by vendor/bin/mago guard (bundled into composer mago; zero baselines). The rules live in mago.toml under [guard.perimeter] โ a forbidden use statement fails the build, not a reviewer.
Production code under Waffle\Commons\EventDispatcher may depend only on:
Waffle\Commons\EventDispatcher\**โ itselfWaffle\Commons\Contracts\**โ the shared contracts package, the only Waffle dependency permittedPsr\**โ PSR interfaces (PSR-14)@global+Psl\**โ PHP core and the PHP Standard Library
Test code under WaffleTests\Commons\EventDispatcher is unrestricted (@all). Structural rules are guarded too: interfaces must be named *Interface, Exception\** classes must end in *Exception, and any Enum\** namespace may hold only enum declarations.
Contract-first, component-agnostic by construction: components compose through waffle-commons/contracts, never directly through one another.
๐งช Testing
docker exec -w /waffle-commons/event-dispatcher waffle-dev composer tests
๐ License
MIT โ see LICENSE.md.