Packagesv2.0
Events
High-performance PSR-14 event dispatcher with typed interceptors, attribute-based listeners, event sourcing, circuit breakers, and dispatch metrics for the MonkeysLegion framework.
Installation
composer require monkeyscloud/monkeyslegion-events:^2.0
Features
| Feature | Description |
|---|---|
| PSR-14 Compliant | Full EventDispatcherInterface + StoppableEventInterface |
| 5 PHP Attributes | #[Listener], #[Subscriber], #[ListenWhen], #[BeforeEvent], #[AfterEvent] |
| Priority + FIFO | Higher priority runs first; equal priority preserves registration order |
| One-Shot Listeners | once() — auto-removed after first invocation |
| Wildcard Listeners | Pattern matching: User* catches UserCreated, UserDeleted, etc. |
| Event Subscribers | Multi-event handler classes (Laravel + Symfony parity) |
| Stoppable Events | $event->stopPropagation() halts the listener chain |
| Interceptor Pipeline | NOVEL — Before/After hooks (AOP-style) |
| Conditional Listeners | NOVEL — #[ListenWhen] with guard method |
| Circuit Breaker | NOVEL — Auto-disable failing listeners after N failures |
| Event Store/Replay | NOVEL — Record & replay events for testing/debugging |
| Dispatch Metrics | NOVEL — Per-event timing and count tracking |
| Correlation IDs | NOVEL — Hex-based event tracing across request scope |
| Safe Mode | Catch listener exceptions instead of crashing |
| Batch Dispatch | Dispatch multiple events in sequence |
| ShouldQueue Marker | Flag listeners for async/queue processing |
| ShouldBroadcast | Flag events for WebSocket/SSE broadcasting |
| PHP 8.4 Native | Property hooks, backed enums, asymmetric visibility |
Quick Start
use MonkeysLegion\Events\Event;
use MonkeysLegion\Events\EventDispatcher;
use MonkeysLegion\Events\ListenerProvider;
// Define an event
final class UserCreated extends Event
{
public function __construct(
public readonly string $email,
) {
parent::__construct();
}
}
// Register listeners
$provider = new ListenerProvider();
$provider->add(UserCreated::class, function (UserCreated $event) {
echo "Welcome, {$event->email}!";
});
// Dispatch
$dispatcher = new EventDispatcher($provider);
$dispatcher->dispatch(new UserCreated('jorge@monkeyscloud.com'));
Attribute-Based Listeners
use MonkeysLegion\Events\Attribute\Listener;
#[Listener(event: UserCreated::class, priority: 10)]
final class SendWelcomeEmail
{
public function __invoke(UserCreated $event): void
{
// Send email to $event->email
}
}
// Register via attribute scanning
$provider->addFromAttributes(new SendWelcomeEmail());
Conditional Listeners (Novel)
use MonkeysLegion\Events\Attribute\Listener;
use MonkeysLegion\Events\Attribute\ListenWhen;
#[Listener(event: OrderPlaced::class)]
#[ListenWhen(method: 'isHighValue')]
final class OnHighValueOrder
{
public function isHighValue(OrderPlaced $event): bool
{
return $event->amount > 1000;
}
public function __invoke(OrderPlaced $event): void
{
// Only called when amount > 1000
}
}
Interceptor Pipeline (Novel)
AOP-style before/after hooks that wrap the regular listener chain:
use MonkeysLegion\Events\Attribute\BeforeEvent;
use MonkeysLegion\Events\Attribute\AfterEvent;
final class OrderInterceptor
{
#[BeforeEvent(event: OrderPlaced::class)]
public function validate(OrderPlaced $event): void
{
// Runs BEFORE regular listeners
}
#[AfterEvent(event: OrderPlaced::class)]
public function audit(OrderPlaced $event): void
{
// Runs AFTER all regular listeners
}
}
$provider->addFromAttributes(new OrderInterceptor());
Global Interceptors
use MonkeysLegion\Events\Interceptor\TimingInterceptor;
use MonkeysLegion\Events\Interceptor\LoggingInterceptor;
use MonkeysLegion\Events\EventMetrics;
$metrics = new EventMetrics();
$dispatcher->addInterceptor(new TimingInterceptor($metrics));
$dispatcher->addInterceptor(new LoggingInterceptor($psrLogger));
$dispatcher->dispatch(new OrderPlaced(42, 99.99));
echo $metrics->countFor(OrderPlaced::class); // 1
echo $metrics->averageFor(OrderPlaced::class); // 0.123 ms
Event Subscribers
use MonkeysLegion\Events\EventSubscriberInterface;
final class UserEventSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
UserCreated::class => 'onUserCreated',
UserDeleted::class => ['onUserDeleted', 10], // with priority
OrderPlaced::class => [
['onOrderLog', 10],
['onOrderNotify', 5],
],
];
}
public function onUserCreated(UserCreated $event): void { /* ... */ }
public function onUserDeleted(UserDeleted $event): void { /* ... */ }
public function onOrderLog(OrderPlaced $event): void { /* ... */ }
public function onOrderNotify(OrderPlaced $event): void { /* ... */ }
}
$provider->addSubscriber(new UserEventSubscriber());
Wildcard Listeners
// Matches UserCreated, UserDeleted, UserUpdated, etc.
$provider->addWildcard('*UserCreated', function (object $event) {
// Handle any event matching the pattern
});
Stoppable Events
$provider->add(GenericEvent::class, function (Event $event) {
$event->stopPropagation(); // Subsequent listeners won't run
}, priority: 10);
$provider->add(GenericEvent::class, function () {
// This will NOT be called
}, priority: 0);
Event Store & Replay (Novel)
use MonkeysLegion\Events\Store\EventStore;
$store = new EventStore();
$dispatcher = new EventDispatcher($provider, store: $store);
$dispatcher->dispatch(new UserCreated('a@test.com'));
$dispatcher->dispatch(new OrderPlaced(1, 50.0));
// Query recorded events
$users = $store->ofType(UserCreated::class); // [UserCreated]
echo $store->size; // 2
// Replay all events through another dispatcher
$store->replay($anotherDispatcher);
Dispatch Result & Metrics
$result = $dispatcher->dispatchWithResult(new UserCreated('test@test.com'));
echo $result->listenersInvoked; // 3
echo $result->durationMs; // 0.456
echo $result->stopped; // false
echo $result->isClean(); // true (no errors)
// Batch dispatch
$results = $dispatcher->dispatchBatch([
new UserCreated('a@test.com'),
new OrderPlaced(1, 50.0),
]);
Circuit Breaker (Novel)
Listeners that fail repeatedly are auto-disabled:
$descriptor = new ListenerDescriptor(
listener: fn() => throw new \RuntimeException('fail'),
eventClass: OrderPlaced::class,
circuitThreshold: 3, // Trip after 3 failures
circuitResetTime: 60, // Try again after 60 seconds
);
Safe Mode
// Catch listener exceptions instead of crashing
$dispatcher = new EventDispatcher($provider, safeMode: true);
$result = $dispatcher->dispatchWithResult(new OrderPlaced(1, 50.0));
// Errors captured in $result->errors instead of throwing
Async/Queue Markers
use MonkeysLegion\Events\Contract\ShouldQueue;
#[Listener(event: OrderPlaced::class)]
final class ProcessPayment implements ShouldQueue
{
public function __invoke(OrderPlaced $event): void
{
// Will be dispatched to queue by integration layer
}
}
Correlation Tracking
$event = new UserCreated('test@test.com');
echo $event->correlationId; // "a1b2c3d4..." (auto-generated 32-char hex correlation ID)
echo $event->name; // "UserCreated" (auto-derived)
echo $event->timestamp; // DateTimeImmutable
PHP 8.4 Features Used
| Feature | Where |
|---|---|
| Property Hooks | Event::$isPropagationStopped, Event::$name, EventMetrics, ListenerProvider::$count, EventStore::$size, EventDispatcher::$totalDispatches |
| Asymmetric Visibility | Event::$timestamp, Event::$correlationId, ListenerDescriptor |
| Backed Enum | EventType (Before/On/After) |
readonly classes | DispatchResult, all Attributes |
match expressions | EventType::label() |
new in initializers | Event::$timestamp, ListenerDescriptor::$registeredAt |
| PHP 8 Attributes | 5 attributes across Attribute/ namespace |
Changelog
2.0.0 — Complete Rebuild
BREAKING CHANGE: Full API redesign from v1.
- Architecture: Replaced minimal PSR-14 shim with full interceptor-pipeline dispatcher
- 5 Attributes:
#[Listener],#[Subscriber],#[ListenWhen],#[BeforeEvent],#[AfterEvent] - Contracts:
ShouldQueue,ShouldBroadcast,EventSubscriberInterface - Interceptors:
InterceptorInterface,LoggingInterceptor,TimingInterceptor - Event Store: In-memory record & replay for testing/debugging/event sourcing
- Novel Features: Conditional listeners, circuit breaker, wildcard matching, correlation tracking, dispatch metrics, batch dispatch, safe mode
- PHP 8.4: Property hooks, backed enums, asymmetric visibility,
newin initializers - Tests: 59 tests, 119 assertions
Requirements
- PHP 8.4+
psr/event-dispatcher^1.0
Optional
psr/log^3.0 — ForLoggingInterceptor
License
MIT