Usage

Core patterns and workflows for WPZylos Events.


Architecture Overview

WPZylos Events follows the PSR-14 standard with a clear separation of concerns:

ComponentResponsibility
ListenerProviderRegisters and stores listeners
EventDispatcherDispatches events to listeners
StoppableEventBase class for events that can be stopped
EventServiceProviderWires 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. Use EventServiceProvider::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 calls stopPropagation() 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 {}