Skip to main content

Publishing events

Publish an event​

Dirthara\Events\Contract\EventPublisher announces an event without the caller depending on when or how it is handled:

use Dirthara\Events\Contract\EventPublisher;

final readonly class RegisterUser
{
public function __construct(
private EventPublisher $events,
) {}

public function __invoke(UserId $userId): void
{
// ...

$this->events->publish(new UserWasCreated($userId));
}
}

publish() takes any object and returns nothing. Events stay plain PHP objects, exactly as they are for dispatching.

Publishing and dispatching are different operations​

dispatch() runs the listeners now. publish() is a notification, and the contract promises nothing about what happens to the event afterwards.

dispatch()publish()
Defined byPSR-14 EventDispatcherInterfaceEventPublisher
Listeners have run when it returnsYesNot promised
Changes listeners make to the event are visible to the callerYesNot promised
Propagation can be stoppedYesNot promised
Listener exceptions reach the callerYesNot promised
ReturnsThe eventNothing

Dispatch an event when the calling code relies on its listeners, for example to collect the warnings they leave on it:

$event = $dispatcher->dispatch(new PasswordWasChanged($user));

Publish an event when the calling code only announces that something happened, and would be correct whether its listeners ran now, later in the same process, or somewhere else:

$publisher->publish(new UserWasCreated($userId));

Code typed against EventPublisher keeps working unchanged when the application replaces its publisher with one that handles events differently.

Publish synchronously​

SynchronousEventPublisher publishes an event by dispatching it straight away:

use Dirthara\Events\EventDispatcher;
use Dirthara\Events\ListenerProvider;
use Dirthara\Events\SynchronousEventPublisher;

$provider = new ListenerProvider();
$publisher = new SynchronousEventPublisher(new EventDispatcher($provider));

$publisher->publish(new UserWasCreated($userId));

The constructor accepts any PSR-14 EventDispatcherInterface, so the publisher also works with a dispatcher from another library. publish() dispatches the event it is given once, and ignores what dispatch() returns.

Because the dispatch is synchronous, so is the publish: every listener has run by the time publish() returns, and an exception thrown by a listener or by the dispatcher leaves publish() unchanged. That is how this publisher happens to behave, not what EventPublisher promises. Code that publishes an event should not rely on it.

To handle published events later in the same process instead, wrap the publisher in a DeferredEventPublisher.

Publish immutable events​

A published event may be handled after publish() returns, so nothing the caller does to it afterwards, and nothing a listener does to it, can safely be relied on. Published events should be immutable notifications that describe what happened:

final readonly class UserWasCreated
{
public function __construct(
public UserId $userId,
) {}
}

The package does not enforce this, and publish() accepts a mutable object like any other.

tip

Carry identifiers and values rather than services or open resources. An event that only holds data can be handled in another process as easily as in this one.

Asynchronous publishing​

This package does not queue events or hand them to another process. It has no queue, worker, serialiser, retry, or delivery guarantee, and no dependency on any transport.

EventPublisher leaves room for that. Another package can implement it on top of a queue:

use Dirthara\Events\Contract\EventPublisher;

final readonly class QueuedEventPublisher implements EventPublisher
{
public function publish(object $event): void
{
// hand the event to a queue
}
}

Code that publishes through EventPublisher does not change when an application swaps SynchronousEventPublisher for such a publisher.

Listeners themselves stay synchronous: ListenerProvider::listen() has no option to queue or defer one listener. A listener that wants to hand work elsewhere can publish another event through a publisher that does.