Exposing webhooks via mezzio-swoole

  • You are here: Free PHP » Uncategorized » Exposing webhooks via mezzio-swoole

I was first introduced to the concept of webhooks via a 2009 blog post by John Herren, a former colleague at Zend. At the time, they were in their infancy; today, they're ubiquituous, as they provide a mechanism for a service to notify interested parties of events. This saves traffic; instead of consumers polling an API for event changes, the service notifies them directly. It also means that the consumer does not need to setup things like cronjobs; they instead setup a webhook endpoint, register it with the service provider, and their application takes care of the rest.

The thing is, handling a webhook can often lead to additional processing, and you are expected to send an immediate response to the provider indicating you received the event.

How can you achieve this?

Offloading processing

It's likely no secret that I'm a fan of Mezzio and OpenSwoole1. Running PHP in a persistent process forces me to think about state in my applications, which in turn generally forces me to be more careful and explicit in how I code things. On top of that, I get the benefit of persistent caching, better performance, and more.

One feature I pushed into mezzio-swoole (the Swoole and OpenSwoole bindings for Mezzio) was functionality for working with swoole task workers. There's a variety of ways to use the functionality, but my favorite is by using a PSR-14 EventDispatcher to dispatch an event to which I attach deferable listeners.

What does that look like?

Let's say I have a GitHubWebhookEvent, for which I have associated a GitHubWebhookListener2 in my event dispatcher. I would dispatch this event as follows:

/** @var GitHubWebhookEvent $event */
$dispatcher->dispatch($event);

The nice part about this is that the code dispatching the event does not need to know how the event is processed, or even when. It just dispatches the event and moves on.

To make the listener deferable, in Mezzio applications, I can associate a special delegator factory provided by the mezzio-swoole package with the listener. This is done with standard Mezzio dependency configuration:

use Mezzio\Swoole\Task\DeferredServiceListenerDelegator;

return [
    'dependencies' => [
        'delegators' => [
            GitHubWebhookListener::class => [
                DeferredServiceListenerDelegator::class,
            ],
        ],
    ],
];

This approach means that my listener can have any number of dependencies, and be wired into the container, but when I request it, I'll be returned a Mezzio\Swoole\Task\DeferredServiceListener instead. This class will create a swoole task from the listener and event, which defers execution to the task workers, offloading it from the web workers.

Event state

Task workers receive a copy of the event, not the original instance. Any state changes your listener makes in the event instance will not be reflected in the instance present in your web workers. As such, you should only defer listeners that do not communicate state back to the dispatching code via the event.

Sharing an event dispatcher with the web server

mezzio-swoole defines a marker interface, Mezzio\Swoole\Event\EventDispatcherInterface. This interface is used to define an event-dispatcher service consumed by Mezzio\Swoole\SwooleRequestHandlerRunner for the purpose of dispatching swoole HTTP server events, getting around the "one event, one handler" rule swoole follows. However, that can mean that you end up with two different dispatchers in your application: one used by the swoole web server, and one by the application, and that means you cannot delegate tasks.

To get around this, alias the Mezzio\Swoole\Event\EventDispatcherInterface service to the Psr\EventDispatcher\EventDispatcherInterface service:

use Mezzio\Swoole\Event\EventDispatcherInterface as SwooleEventDispatcher;
use Psr\EventDispatcher\EventDispatcherInterface as PsrEventDispatcher;

return [
    'dependencies' => [
        'alias' => [
            SwooleEventDispatcher::class => PsrEve

Truncated by Planet PHP, read more at the original (another 6872 bytes)

Powered by Gewgley