12/08/2018, 14:34

Event-driven PHP engines

Traditional PHP serving style assumes it to always run behind web-server, which uses default PHP interpreter or FPM pool to handle requests. This brings limitations, which are the main arguments in criticizing PHP: Every request is being handled separately and memory is freed after request. All ...

Traditional PHP serving style assumes it to always run behind web-server, which uses default PHP interpreter or FPM pool to handle requests. This brings limitations, which are the main arguments in criticizing PHP:

  1. Every request is being handled separately and memory is freed after request. All the code needs to be recompiled and re-executed.
  2. Most asynchronous features like tasks or realtime interactions require using third-party languages like nodejs.

On the opposite to FPM, event-driven PHP works more like most concurrent engines (Node.js for example) and serves from PHP itself.

There are 2 most popular alternatives: Workerman, which is written with PHP and Swoole, available as PECL component and written in C.

Swoole includes components for different purposes: Server, Task Worker, Timer, Event and Async IO. With these components, Swoole allows you to build many features.

The following code will launch a basic echo server.

// create a server instance
$serv = new swoole_server("127.0.0.1", 9501); 

// attach handler for connect event, once client connected to server the registered handler will be executed
$serv->on('connect', function ($serv, $fd){  
    echo "Client:Connect.
";
});

// attach handler for receive event, every piece of data received by server, the registered handler will be
// executed. And all custom protocol implementation should be located there.
$serv->on('receive', function ($serv, $fd, $from_id, $data) {
    $serv->send($fd, $data);
});

$serv->on('close', function ($serv, $fd) {
    echo "Client: Close.
";
});

// start our server, listen on port and ready to accept connections
$serv->start();

Looks a lot like Express.js code, right?

For HTTP the special swoole_http_server should be created. It allows to listen to HTTP requests and handle them with a Closure having two arguments: function (swoole_http_request $request, swoole_http_response $response) { … }

$http = new swoole_http_server("0.0.0.0", 9501);

$http->on('request', function ($request, $response) {
    $response->header("Content-Type", "text/html; charset=utf-8");
    $response->end("<h1>Hello Swoole. #".rand(1000, 9999)."</h1>");
});

$http->start();

The swoole_http_request object contains all expected data from request: $get, $post, $header, $server, $cookie, $files, $fd as key-value arrays.

This allows to simply map the request object to PSR-7 or Symfony HTTP Request or any other framework.

The swoole_http_response object allows to pass the result to server response. The class provides following methods:

  • end() — End Http response, send HTML content
  • write() — Send Http chunk of data to the browser
  • header() — Add a header to response
  • cookie() — Set a cookie
  • status() — Set Http Status Code, such as 404, 501, 200
  • gzip() — Set http compression format

Websockets can also be easily implemented and run with PHP-only environment.

$ws = new swoole_websocket_server("0.0.0.0", 9502);

$ws->on('open', function ($ws, $request) {
    var_dump($request->fd, $request->get, $request->server);
    $ws->push($request->fd, "hello, welcome
");
});

$ws->on('message', function ($ws, $frame) {
    echo "Message: {$frame->data}
";
    $ws->push($frame->fd, "server: {$frame->data}");
});

$ws->on('close', function ($ws, $fd) {
    echo "client-{$fd} is closed
";
});

$ws->start();

Both Workerman and Swoole can be easily integrated with Laravel, Symphony and Yii2 by using third-party packages.

For Laravel garveen/laravoole is available and supports Laravel versions up to 5.3.

Developers of the engines promise up to 10x performance increase comparing to default PHP-FPM configuration.

This makes sense as PHP code is not being unloaded from memory anymore and doesn’t require continuous compilation on every request.

In comparison of Swoole and Workerman, the first gives slightly higher performance on the same configuration:

swoolehttpserver
Requests per second:    67213.61 [#/sec] (mean)

walkor/Workerman
Requests per second:    41669.86 [#/sec] (mean)
  • GitHub - swoole/swoole-src: Event-driven asynchronous & concurrent & coroutine networking engine with high performance for PHP.
  • GitHub - walkor/Workerman: An asynchronous event driven PHP framework for easily building fast, scalable network applications. Supports HTTP, Websocket and other custom protocols. Supports libevent, HHVM, ReactPHP.
  • GitHub - garveen/laravoole: Laravel && ( Swoole || Workerman ) to get 10x faster than php-fpm
0