Async Expressive

Matthew Weier O'Phinney


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


Demonstrate how async enables

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



event loop

Event Loop; image copyright Bartolomeo Sorrentino


Shared Nothing

Shared Nothing

Message Queues

Message Queue; image copyright Stackify

Parallel Processing

Parallel Processing; image copyright Michael Kloran

Async Programming



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

Event Dispatcher

$dispatcher->addListener(function (SomeEvent $e) {
    // do work

// later:

Callback Hell

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


$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

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

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");

$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.


$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 $handler;   // callable
    public $arguments; // array

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


A note on debugging

Coroutine support in Swoole is incompatible with XDebug and XHProf!

What is Expressive?

Expressive is a middleware application framework built on PSR-15

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

Expressive also provides...

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

Middleware + Swoole

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

Codified in zend-expressive-swoole

Starting your Expressive+Swoole server

$ ./vendor/bin/zend-expressive-swoole start

Async Operations


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



Task Workers



use Phly\Swoole\TaskWorker\Task;

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

Listener deferment

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

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

In your own code:



return [
    'zend-expressive-swoole' => [
        'enable_coroutine' => true,
        'swoole-http-server' => [
            'mode' => SWOOLE_PROCESS,

$ ./vendor/bin/zend-expressive-swoole start --daemonize


# DOCKER-VERSION        1.3.2

FROM mwop/phly-docker-php-swoole:7.2-alpine

# Project files
COPY . /var/www/

# Reset "local"/development config files
WORKDIR /var/www
RUN rm -f config/development.config.php && \
  rm config/autoload/*.local.php && \
  mv config/autoload/local.php.dist config/autoload/local.php

# Overwrite entrypoint
RUN echo "#!/usr/bin/env bash
  (cd /var/www ; ./vendor/bin/zend-expressive-swoole start)
" > /usr/local/bin/entrypoint


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:

$allowed = $this->acl->isUserAllowed(

or the request:

$allowed = $this->acl->isRequestAllowed($request);

Request Attributes

Instead of:

$template->addDefaultParam(/* ... */);

We use a request attribute

public function process(
    Request $request,
    RequestHandler $handler
) : Response {
    return $handler->handle(
        $request->withAttribute('template_params', (object) [])

Request Attributes (cont.)

to which we push data:

$params = $request->getAttribute('template_params');
$params->user = $this->deriveUserFromRequest($request);
return $handler->handle($request);

Request Attributes (cont.)

$templateParams = $request->getAttribute('template_params');
return new HtmlResponse($this->renderer->render(
    array_merge((array) $templateParams, [
        // more specific parameters


Use zend-expressive-session

and its zend-expressive-session-cache adapter

Wrap Up


  • Single bootstrap
  • Deferment
  • Single container services


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


Thank You!

Contact me at

I tweet @mwop