Async Middleware

Matthew Weier O'Phinney

SunshinePHP 2020


  • PHP-FIG (or FIG): Framework Interop Group (standards body)
  • PSR-7: HTTP Message interfaces
  • PSR-15: HTTP Request Handler/Middleware interfaces
  • PSR-17: HTTP Message Factory interfaces
  • PSR-14: Event Dispatcher interfaces


I will demonstrate how async enables:

  • Application-specific servers
  • Better performance, generally due to...
  • Deferred processing.



event loop

Event Loop; image copyright Bartolomeo Sorrentino


PHP: Shared Nothing

Shared Nothing
  • Bootstraps. Every. Request.
  • Long-running processes delay the response.

Message Queues

Message Queue; image copyright Stackify

Parallel Processing

Parallel Processing; image copyright Michael Kloran

Async Programming

Styles of Async Programming

Four general styles:

  • Callbacks
  • Promises
  • async/await
  • Coroutines

Deferment patterns



function ($error, $result)
    if ($error) {

Callback Hell

doFirst($payload, function ($err, $result) {
    doSecond($result, function ($err, $result) {
        doThird($result, function ($err, $result) {
            // finally got what we needed

aka the "Pyramid of Doom"


$promise = someAsyncOperationReturningAPromise($with, $data);


function then($successCallback = null, $rejectionCallback = null);

and catch() is a shortcut for:

$promise->then(null, $someCallbackToExecuteOnRejection);


async function ping() {
  const res = await fetch('/api/ping');
  return await res.json();

let ack = ping();


$result = $statement->execute($data);

Swoole provides...

  • an event loop
  • async HTTP, network, and socket clients
  • network servers

Swoole features

  • Coroutine support for many TCP/UDP and socket operations
  • Spawning multiple workers per server
  • Spawning separate task workers
  • Daemonization of servers

A Basic Web Server

use Swoole\Http\Server as HttpServer;

$server = new HttpServer('', 9000);
$server->on('start', function ($server) {
    echo "Server started at\n";
$server->on('request', function ($request, $response) {
    $response->header('Content-Type', 'text/plain');
    $response->end("Hello World\n");


$server->defer(function () {
    // work to defer

Task Workers

Prepare the server
for tasks

  • Configure the number of task workers to use (required!)
  • Register a listener to handle incoming tasks.
  • Register a listener to execute on task completion.

Registering task workers

$server->set(['task_worker_num' => 4]);
$server->on('task', function ($server, $taskId, $data) {
    // Handle task

    // Finish task:
$server->on('finish', function ($server, $taskId, $returnValue) {
    // Task is complete

Triggering a task


A Basic TaskWorker

class Task {
    public callable $handler;
    public array $arguments;

$server->on('task', function ($server, $taskId, $task) {
    if (! $task instanceof Task) {



Code Reloading

or lack thereof


Coroutine support in Swoole is incompatible with XDebug and XHProf!

Unit Testing

Mocking Swoole classes is difficult.

One Listener Per Event

$response->end() is Problematic

If you forget to call it:

  • the connection will remain open until a network timeout occurs;
  • the current process will remain open; which means
  • no next tick of the event loop.

Non-Standard Request/Response API

It is none of:

  • PSR-7
  • Symfony HttpKernel
  • laminas-http / zend-http

Swoole Pros and Cons

Pros Cons
Multiple web workers No hot-code reloading
Separate task workers Unmockable classes
Coroutine support Single-listener events
Bootstrap elimination $response->end()
No web server Non-standard request/response API
Performance gains


Request Handlers and Middleware

namespace Psr\Http\Server;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;

interface RequestHandlerInterface {
    public function handle(Request $request) : Response;

interface MiddlewareInterface {
    public function process(
        Request $request,
        RequestHandlerInterface $handler
    ) : Response;

Middleware Flow

Middleware; image copyright Sergey Zhuk

Middleware + Swoole

$server->on('request', function ($request, $response) use ($app) {
    $appResponse = $app->handle(transformRequest($request));
    transformResponse($response, $appResponse);


A middleware application runner, with...

  • Dependency Injection wiring abstraction
  • Routing abstraction
  • Template abstraction
  • Error handling abstraction
  • Application and per-route pipelines


Starting your Mezzio+Swoole server

$ ./vendor/bin/mezzio-swoole start

Configurable Server Features

  • Limited hot-code reloading
  • Static file serving

Async Applications

  • Eliminating bootstrap operations
  • Deferred operations

Deferred Operations


$result = $mysqli->query($sql);
while ($data = $result->fetch_assoc()) {
    // ...



Task Workers



phly-swoole-taskworker Usage

use Phly\Swoole\TaskWorker\Task;

$server->task(new Task(function ($event) {
    // handle the event
}, $event));

Listener deferment


$listener = new DeferredListener($server, $listener);

// Where DeferredListener is equivalent to:
function (object $event) use ($server, $listener) : void {
    $server->task(new Task($listener, $event));

In your own code:



Pitfalls; image copyright Activision

Stateful Services

Stateful Services: Templating

$template->addDefaultParam('*', 'user', $user);

$metadata = $resourceGenerator
    ['query' => $query]

Stateful Services: Validation

if (! $validator->isValid($value)) {
    $messages = $validator->getMessages();

echo implode("
", $validator->getMessages())

Stateful Services: Auth

return $handler(
    $request->withAttribute('user', $auth->getIdentity())

Resolving State Issues


class StatelessVariant implements SomeInterface
    private $proxy;

    public function __construct(SomeInterface $proxy)
        $this->proxy = $proxy;

public function morphState($data) : void
    throw new CannotChangeStateException();


class StatelessVariant extends OriginalClass
    public function morphState($data) : void
        throw new CannotChangeStateException();

Factories as Services

public function __construct(SomeClass $dependency) : void


public function __construct(SomeClassFactory $factory) : void

and we then:

$dependency = ($this->factory)();

Stateful Messages

Pass stateful data to the service:

$result = $this->router->route(

or the request:

$result = $this->router->route(

Pass State Via Request Attributes

public function process(
    Request $request,
    RequestHandler $handler
) : Response {
    $result = $this->router->route($request);
    return $handler->handle(
        $request->withAttribute(RouteResult::class, $result)

Using Request Attributes

$routeResult = $request->getAttribute(RouteResult::class);
return new HtmlResponse($this->renderer->render(
    [ 'routeResult' => $routeResult ]


Use mezzio-session

and its mezzio-session-cache adapter

Wrap Up


  • Eliminating bootstrap operations
  • Deferred operations
  • Crazy fast performance!


  • Abstract async-specific details.
  • Make container services stateless
  • Aggregate state in the request
  • Avoid the session extension


Thank You!

Contact me at

I tweet @mwop