The olvlvl/delayed-event-dispatcher package provides an event dispatcher that delays event dispatching to a later time in the application life.

A delayed event dispatcher is useful to reduce the response time of your HTTP application when events can perfectly be dispatched after the response has been sent. For instance, updating an entity that would require clearing cache, performing projections, or reindexing, all of which have nothing to do with the response itself.

Because you're probably using one event dispatcher for your application you don't want all events to be delayed, most of them have to be dispatched immediately for your application to run properly, that's why you can specify an arbiter to determine which events to delay and which not to.

Finally, because you want all delayed events to be dispatched when you flush them—even when an exception is thrown—you can provide an exception handler. It's up to you to decide what to do with them. You'll probably want to recover, log the exception, and continue with dispatching the other events.

Disclaimer: The delayed event dispatcher is a decorator that is meant to be used together with symfony/event-dispatcher.

Instantiating a delayed event dispatcher

The delayed event dispatcher is a decorator, which means you'll need another event dispatcher to decorate. Methods are forwarded to the decorated instance, except of course for the dispatch() method.

<?php

use olvlvl\DelayedEventDispatcher\DelayedEventDispatcher;

/* @var \Symfony\Component\EventDispatcher\EventDispatcherInterface $eventDispatcher */

$delayedEventDispatcher = new DelayedEventDispatcher($eventDispatcher); 
$delayedEventDispatcher->addListener('my_event', $listener);

Instantiating an inactive delayed event dispatcher

By default the delayed event dispatcher is active, which means it will delay events. This is fine for your HTTP application, but that's not something you want for the console or the consumer application. You can create a disabled delayed event dispatcher be defining the disabled option as true.

<?php

use olvlvl\DelayedEventDispatcher\DelayedEventDispatcher;

/* @var \Symfony\Component\EventDispatcher\EventDispatcherInterface $eventDispatcher */

$disabledDelayedEventDispatcher = new DelayedEventDispatcher($eventDispatcher, true);

This is especially useful when your HTTP/console/consumer applications are deployed using the same artifact, that you can customize using environment variables.

<?php

use olvlvl\DelayedEventDispatcher\DelayedEventDispatcher;

/* @var \Symfony\Component\EventDispatcher\EventDispatcherInterface $eventDispatcher */

$disabledDelayedEventDispatcher = new DelayedEventDispatcher(
    $eventDispatcher, 
    filter_var(getenv('MYAPP_DISABLE_DELAYED_EVENT_DISPATCHER'), FILTER_VALIDATE_BOOLEAN)
);

Dispatching delayed events

Delayed events are dispatched with the flush() method.

<?php

use olvlvl\DelayedEventDispatcher\DelayedEventDispatcher;

/* @var DelayedEventDispatcher $delayedEventDispatcher */

$delayedEventDispatcher->dispatch('my_event');
$delayedEventDispatcher->dispatch('my_other_event');
$delayedEventDispatcher->flush();

Deciding which events to delay and which not to

By default all events are delayed—if the delayed event dispatcher was created with disabled = false—but you can supply an arbiter to choose which events to delay and which not to. You can use the Delayable interface to mark your events, but it's not a requirement. The arbiter is a simple callable, its implementation is up to you.

<?php

use olvlvl\DelayedEventDispatcher\Delayable;
use olvlvl\DelayedEventDispatcher\DelayedEventDispatcher;
use Symfony\Component\EventDispatcher\Event;

$arbiter = function (string $eventName, Event $event) {
    return $event instanceof Delayable;
};

/* @var \Symfony\Component\EventDispatcher\EventDispatcherInterface $eventDispatcher */

$disabledDelayedEventDispatcher = new DelayedEventDispatcher($eventDispatcher, false, $arbiter);

Handling exceptions

By default exceptions thrown during the dispatching of events are not recovered, the dispatching halts, leaving delayed events in the queue. If you want to recover from these exceptions, and make sure all the events are dispatched, you'll want to provide and exception handler.

<?php

use olvlvl\DelayedEventDispatcher\DelayedEventDispatcher;
use Symfony\Component\EventDispatcher\Event;

/* @var \Psr\Log\LoggerInterface $logger */

$exceptionHandler = function (\Throwable $error, string $eventName, Event $event = null) use ($logger) {
    // The exception is recovered, we log it to fix it later
    $logger->danger($error);
};

/* @var \Symfony\Component\EventDispatcher\EventDispatcherInterface $eventDispatcher */

$disabledDelayedEventDispatcher = new DelayedEventDispatcher($eventDispatcher, false, null, $exceptionHandler);