phpdot/event

PSR-14 event dispatcher with attribute-based listener discovery, async dispatch, ordering, and persistence abstraction.

Maintainers

Package info

github.com/phpdot/event

Issues

pkg:composer/phpdot/event

Transparency log

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

v0.1.0 2026-07-17 23:14 UTC

This package is auto-updated.

Last update: 2026-07-18 03:24:45 UTC


README

A PSR-14 event dispatcher where the #[Listener] attribute is the registration — no central config, no service providers. Listeners are ordered, can run synchronously or be queued for async dispatch, and the listener set is resolved through a repository so it can be persisted and toggled at runtime.

Table of Contents

Requirements

Requirement Constraint
PHP >= 8.5
psr/container ^2.0
psr/event-dispatcher ^1.0
psr/log ^3.0

Installation

composer require phpdot/event

Usage

Events and listeners

An event is any class. A handler declares what it handles with #[Listener] — the attribute is the registration:

use PHPdot\Event\Attribute\Listener;

final readonly class UserRegistered
{
    public function __construct(public int $userId, public string $email) {}
}

#[Listener(UserRegistered::class, order: 1)]
final class SendWelcomeEmail
{
    public function __construct(private MailerInterface $mailer) {}

    public function __invoke(UserRegistered $event): void
    {
        $this->mailer->send($event->email, 'Welcome!');
    }
}

Dispatching

use PHPdot\Event\EventDispatcher;
use PHPdot\Event\ListenerProvider;

$provider = new ListenerProvider();
$provider->addListener(UserRegistered::class, SendWelcomeEmail::class, order: 1);

$dispatcher = new EventDispatcher($provider, $container, $asyncDispatcher, $logger);
$dispatcher->dispatch(new UserRegistered(userId: 1, email: 'omar@example.com'));

Listeners run in order (lowest first). An event implementing StoppableEventInterface halts the chain once propagation is stopped, per PSR-14.

Async listeners

A listener marked #[Listener(..., async: true)] is handed to the injected AsyncDispatcherInterface instead of running inline. SyncOnlyDispatcher is the built-in fallback that resolves and runs the handler synchronously; a Swoole or queue-backed implementation can dispatch it off the request path.

Architecture

EventDispatcher asks the ListenerProvider for the ordered listeners of an event, resolves each handler class through the container, and either invokes it synchronously or hands it to the async dispatcher. The listener set itself comes from a repository, so it can be discovered from attributes, persisted, and toggled without touching code.

graph TD
    EVENT["Event object<br/><br/>any class; StoppableEventInterface halts the chain"]
    DISPATCHER["EventDispatcher<br/><br/>PSR-14: orders, resolves, invokes"]
    PROVIDER["ListenerProvider<br/><br/>ordered listeners for an event"]
    REPO["ListenerRepositoryInterface<br/><br/>attribute discovery + persistence"]
    SYNC["Synchronous invoke<br/><br/>container-resolved handler"]
    ASYNC["AsyncDispatcherInterface<br/><br/>queue / SyncOnlyDispatcher fallback"]

    EVENT --> DISPATCHER
    DISPATCHER --> PROVIDER
    PROVIDER --> REPO
    DISPATCHER --> SYNC
    DISPATCHER --> ASYNC
Loading

Testing

composer install
composer test        # PHPUnit
composer analyse     # PHPStan, level max + strict rules
composer cs-check    # PHP-CS-Fixer
composer check       # All three

License

MIT.

This repository is a read-only mirror, generated by CI from phpdot/monorepo. Pull requests and issues belong in the monorepo.