Usage
Core patterns and workflows for WPZylos Events.
Architecture Overview
WPZylos Events follows the PSR-14 standard with a clear separation of concerns:
| Component | Responsibility |
|---|---|
ListenerProvider | Registers and stores listeners |
EventDispatcher | Dispatches events to listeners |
StoppableEvent | Base class for events that can be stopped |
EventServiceProvider | Wires everything into the container |
Key concept: You register listeners on the ListenerProvider, then dispatch events through the EventDispatcher. The dispatcher has no
listen()method — it only dispatches.
Creating Events
Events are plain PHP objects. No base class or interface is required.
Basic Event
class UserCreated
{
public function __construct(
public readonly User $user
) {}
}
Event with Metadata
class OrderPlaced
{
public function __construct(
public readonly Order $order,
public readonly User $customer,
public readonly \DateTimeImmutable $placedAt = new \DateTimeImmutable()
) {}
}
Registering Listeners
Listeners are registered on the ListenerProvider using addListener().
Closure Listeners
use WPZylos\Framework\Events\ListenerProvider;
$provider = new ListenerProvider();
$provider->addListener(UserCreated::class, function (UserCreated $event) {
mail($event->user->email, 'Welcome!', 'Thanks for signing up.');
});
Class Method Listeners
$provider->addListener(UserCreated::class, [new SendWelcomeEmail(), 'handle']);
$provider->addListener(UserCreated::class, [new NotificationService(), 'notify']);
Invokable Class Listeners
class SendWelcomeEmail
{
public function __invoke(UserCreated $event): void
{
// Send the email
}
}
$provider->addListener(UserCreated::class, new SendWelcomeEmail());
Priority
Lower priority values execute first. Default priority is 10.
// Runs first (priority 1)
$provider->addListener(UserCreated::class, $validateInput, 1);
// Runs second (priority 10, default)
$provider->addListener(UserCreated::class, $sendEmail);
// Runs last (priority 100)
$provider->addListener(UserCreated::class, $logEvent, 100);
Fluent Chaining
addListener() returns $this, so you can chain calls:
$provider
->addListener(UserCreated::class, $sendWelcomeEmail)
->addListener(UserCreated::class, $logCreation, 5)
->addListener(OrderPlaced::class, $processOrder);
Dispatching Events
Events are dispatched through the EventDispatcher. It accepts any object as an event.
Basic Dispatch
use WPZylos\Framework\Events\EventDispatcher;
use WPZylos\Framework\Events\ListenerProvider;
$provider = new ListenerProvider();
$dispatcher = new EventDispatcher($provider);
// Register listeners on the provider
$provider->addListener(UserCreated::class, function (UserCreated $event) {
// Handle user creation
});
// Dispatch the event through the dispatcher
$dispatcher->dispatch(new UserCreated($user));
Capturing the Event After Dispatch
dispatch() returns the event object, which listeners may have modified:
$event = $dispatcher->dispatch(new OrderPlaced($order, $customer));
// Listeners may have set properties on $event
if ($event->wasModified) {
// React to modifications
}
Container Access
When using the WPZylos framework, the EventServiceProvider registers everything automatically.
Resolving from the Container
use Psr\EventDispatcher\EventDispatcherInterface;
use Psr\EventDispatcher\ListenerProviderInterface;
use WPZylos\Framework\Events\EventDispatcher;
use WPZylos\Framework\Events\ListenerProvider;
// By string alias
$dispatcher = $app->make('events');
// By PSR-14 interface
$dispatcher = $app->make(EventDispatcherInterface::class);
// By concrete class
$dispatcher = $app->make(EventDispatcher::class);
// Get the listener provider to register listeners
$provider = $app->make(ListenerProvider::class);
// or
$provider = $app->make(ListenerProviderInterface::class);
Registering Listeners in the Container
// Get the provider from the container and register listeners
$provider = $app->make(ListenerProvider::class);
$provider->addListener(UserCreated::class, function (UserCreated $event) {
// Handle event
});
// Dispatch via the container-resolved dispatcher
$dispatcher = $app->make('events');
$dispatcher->dispatch(new UserCreated($user));
Subscribers
Subscribers group multiple event listeners into a single class by implementing EventSubscriberInterface.
Creating a Subscriber
use WPZylos\Framework\Events\EventSubscriberInterface;
class UserEventSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
UserCreated::class => 'onUserCreated',
UserUpdated::class => 'onUserUpdated',
UserDeleted::class => ['onUserDeleted', 5], // with priority
];
}
public function onUserCreated(UserCreated $event): void
{
// Send welcome email
}
public function onUserUpdated(UserUpdated $event): void
{
// Sync profile changes
}
public function onUserDeleted(UserDeleted $event): void
{
// Clean up user data
}
}
Multiple Handlers for One Event
class OrderSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
OrderPlaced::class => [
['validateStock', 1], // Runs first
['processPayment', 10], // Runs second
['sendConfirmation', 20], // Runs last
],
];
}
public function validateStock(OrderPlaced $event): void { /* ... */ }
public function processPayment(OrderPlaced $event): void { /* ... */ }
public function sendConfirmation(OrderPlaced $event): void { /* ... */ }
}
Registering Subscribers
Subscribers are registered through the EventServiceProvider:
use WPZylos\Framework\Events\EventServiceProvider;
$eventProvider = $app->make(EventServiceProvider::class);
$eventProvider->subscribe(new UserEventSubscriber());
$eventProvider->subscribe(new OrderSubscriber());
Note: Do not call
$dispatcher->addSubscriber()— that method does not exist. UseEventServiceProvider::subscribe()instead.
Stoppable Events
To create events that can halt listener execution, extend the StoppableEvent abstract class.
Creating a Stoppable Event
use WPZylos\Framework\Events\StoppableEvent;
class PaymentValidation extends StoppableEvent
{
public bool $isValid = true;
public string $reason = '';
public function __construct(
public readonly Payment $payment
) {}
public function reject(string $reason): void
{
$this->isValid = false;
$this->reason = $reason;
$this->stopPropagation(); // No further listeners will run
}
}
Stopping Propagation in a Listener
$provider->addListener(PaymentValidation::class, function (PaymentValidation $event) {
if ($event->payment->amount > 10000) {
$event->reject('Amount exceeds limit');
// Remaining listeners will NOT be called
}
});
$provider->addListener(PaymentValidation::class, function (PaymentValidation $event) {
// This listener will be skipped if the previous one stopped propagation
});
Note: The dispatcher checks
isPropagationStopped()before calling each listener. The listener that callsstopPropagation()will still complete, but subsequent listeners are skipped.
Managing Listeners
Check for Listeners
if ($provider->hasListeners(UserCreated::class)) {
// Listeners are registered for this event
}
Clear All Listeners for an Event
$provider->clearListeners(UserCreated::class);
// All listeners for UserCreated are now removed
Event Hierarchy
ListenerProvider supports hierarchical event matching. Listeners registered for a parent class or interface will also be called for child events.
// Base event
abstract class UserEvent
{
public function __construct(public readonly User $user) {}
}
// Child events
class UserCreated extends UserEvent {}
class UserDeleted extends UserEvent {}
// This listener fires for UserCreated AND UserDeleted
$provider->addListener(UserEvent::class, function (UserEvent $event) {
log("User event: " . get_class($event));
});
$dispatcher->dispatch(new UserCreated($user)); // Triggers the listener
$dispatcher->dispatch(new UserDeleted($user)); // Also triggers it
Organizing Events
Namespace-Based Grouping
// App\Events\User\
namespace App\Events\User;
class Created {}
class Updated {}
class Deleted {}
// App\Events\Order\
namespace App\Events\Order;
class Placed {}
class Shipped {}
class Completed {}