André Roaldseth - @androa

Why ReactPHP?

Event-driven non-blocking I/O in PHP. 


A programming paradigm where code 
execution is triggered by events.


Program can work on other task while
 waiting for input/output operations.

Web Scraping

Generates lots of HTTP requests.
Often little or none processing.

What takes time?

andrer@dev:~/http-analyzer (master)$ ./http-analyzer analyze
Status Code: 200
Time spent in 1 redirection(s): 302 ms
Response size:        12.3 KB

  DNS Lookup:           60.9 ms
  Connecting:          188.7 ms
  Sending:               0.1 ms
  Waiting (TTFB):      637.7 ms
  Recieving:             0.6 ms
  Total time:          940.3 ms 

Actual transfer is 0.6 ms. The rest is waiting on network.

Non-blocking I/O to the rescue!

Non-blocking I/O enables you to 
work on other requests while waiting.

But how do you know when it's done?

Through Events!

When an asynchronously non-blocking operation 
has progressed, it will tell you through events.

Typical events for I/O operations are:

Back to ReactPHP

Basically a port of Node.js to PHP.

Well, not really.

Provides a userland implementation
of non-blocking event-driven I/O.

Igor Wiedler - @igorwhiletrue

Some Numbers

The following graphs comes from Phil Sturgeons 
blog post about benchmarking ReactPHP vs Node.js

The Initial Test Case

The linearity of the red (and blue) line is 
caused by consistent network latency.

Wait, What? PHP being faster than JS?

Node's maxConnection setting has been tuned, 
getting it up to speed with PHP.

A simple HTTP Server with ReactPHP

require 'vendor/autoload.php';

$loop = React\EventLoop\Factory::create();
$socket = new React\Socket\Server($loop);
$http = new React\Http\Server($socket, $loop);

$app = function ($request, $response) {
    $response->writeHead(200, array('Content-Type' => 'text/plain'));
    $response->end("Hello World\n");

$http->on('request', $app);

echo "Server running at\n";



 In its simplest definition, a stream is a resource object which exhibits streamable behavior. 

That is, it can be read from or written to in a linear fashion, and may be able to fseek() to an arbitrary locations within the stream.
- PHP Manual

Stream Wrappers

stdin/stdout (CLI)

Process interaction

Readable Streams

Emits these events:


Provides these methods:

pipe(WritableStreamInterface $dest, array $options = [])

Writeable Streams

Emits these events:


Provides these methods:

end($data = null)

The Most Basic Example

$loop = React\EventLoop\Factory::create();

$source = new React\Stream\Stream(fopen('omg.txt', 'r'), $loop);
$dest = new React\Stream\Stream(fopen('wtf.txt', 'w'), $loop);



Reading a Huge File

$redis = new Predis\Client();
$loop = React\EventLoop\Factory::create();

$buffer = '';

$dest = new React\Stream\Stream(fopen('local-copy.txt', 'w'), $loop);
$source = new React\Stream\Stream(fopen('http://internet/4GB-file.txt', 'r'), $loop);
$source->on('data', function($data) use (&$buffer, $redis) {
    $buffer .= $data;

    if (strpos($buffer, PHP_EOL) !== false) {
        foreach (explode(PHP_EOL, $buffer) as $line) {
            $redis->rpush('queue', $line);

        $buffer = '';

Reduced run time from 30 minutes to 7-8 minutes.
Yes, there is a bug in the code :)


Based on CommonJS Promises/A

Consists of three concepts:
  • Deferred
  • Promise
  • Resolver


A Deferred represents a computation or unit of work that may not have completed yet.


While a Deferred represents the computation itself, a Promise represents the result of that computation.


A Resolver can resolve, reject or trigger progress notifications on behalf of a Deferred without knowing any details about consumers.

Create a Promise

function doAsyncHttpRequest($url) {
    $deferred = new React\Promise\Deferred();

    // Pass only the Resolver
    fetchHttpAsynchronously($deferred->resolver(), $url);

    // Return only the Promise, so that the caller cannot
    // resolve, reject, or otherwise work with the original Deferred.
    return $deferred->promise();

Act On The Promise

$promise = doAsyncHttpRequest('');

$promise->then(function($apiResponse) {
    echo 'API responded with ' . $apiResponse;

Resolve The Promise

function doAsyncHttpRequest($resolver, $url) {
    // Perform the HTTP request asynchronously and 
    // resolve the promise using the API response.


Promise API

interface PromiseInterface {
    public function then(
        callable $fulfilledHandler = null, 
        callable $errorHandler = null, 
        callable $progressHandler = null
Returns a new Promise, making it chainable.

Resolver API

interface ResolverInterface {
    public function resolve(mixed $result = null);
    public function reject(mixed $reason = null);
    public function progress(mixed $update = null);}


Used for creation, joining, mapping 
and reducing collections of Promises.

When API

$promise = React\Promise\When::all(
    array|React\Promise\PromiseInterface $promisesOrValues,
    callable $fulfilledHandler = null,
    callable $errorHandler = null,
    callable $progressHandler = null
$promise = React\Promise\When::any(
    array|React\Promise\PromiseInterface $promisesOrValues,
    callable $fulfilledHandler = null,
    callable $errorHandler = null,
    callable $progressHandler = null
$promise = React\Promise\When::some(
    array|React\Promise\PromiseInterface $promisesOrValues,
    integer $howMany,
    callable $fulfilledHandler = null,
    callable $errorHandler = null,
    callable $progressHandler = null

When API

$promise = React\Promise\When::map(
    array|React\Promise\PromiseInterface $promisesOrValues,
    callable $mapFunc
$promise = React\Promise\When::reduce(
    array|React\Promise\PromiseInterface $promisesOrValues,
    callable $reduceFunc,
    $initialValue = null

Partial Function Application

Pre-fills argument(s) to a given function 
and returns a new function.
function multiplyNumbers($a, $b) {
    return $a * $b;
$vat = 1.24;

$priceA = 100;
$priceAWithVat = multiplyNumbers($price, $vat);

$priceB = 300;
$priceBWithVat = multiplyNumbers($price, $vat);
$addVat = React\Partial\bind('multiplyNumbers', 1.24);

$priceA = 100;
$priceAWithVat = $addVat($price);

$priceB = 100;
$priceBWithVat = $addVat($price);

Partial Function Application

Useful for adding arguments you have now,
but not when the function will be used in the future.
$url = '';

$promise = performAsyncGet($url);

function responseHandler($url, $response) {
    // Your handler wants both the $url and $response

$promise->then('responseHandler'); // But.. we have no URL?
$promise->then(function($data) use ($url) {
    responseHandler($url, $data);
$promise->then(React\Partial\bind('responseHandler', $url));

Putting it all together

A rather silly example on how to use 
the different components together.

We want to GET several URLs in parallel and
show how many bytes each where and
create a grand total.

Set up the DNS and HTTP client

require 'vendor/autoload.php';

$loop = React\EventLoop\Factory::create();

$dnsResolverFactory = new React\Dns\Resolver\Factory();
$dnsResolver = $dnsResolverFactory->createCached('', $loop);

$factory = new React\HttpClient\Factory();
$client = $factory->create($loop, $dnsResolver);

Create a function for
handling responses

function handleResponse($resolver, $response) {
    $body = '';

    $response->on('data', function ($data) use (&$body) {
        $body .= $data;

    $response->on('end', function() use (&$body, $resolver) {

Prepare some HTTP requests

$promises = [];

foreach (['', ''] as $url) {
    $deferred = new React\Promise\Deferred();
    $promise = $deferred->promise();

    $promise->then(function($data) use ($url) {
        // Executed when the $url is loaded and does not know about any other requests.
        printf('%s responded with %d bytes' . PHP_EOL, $url, strlen($data));

    $request = $client->request('GET', $url);
    $request->on('response', React\Partial\bind('handleResponse', $deferred->resolver()));
    $promises[] = $promise;

Create a total when all
promises are fulfilled

    function ($value, $data) {
        return strlen($data) + $value;
)->then(function($data) {
    printf('Downloaded %d bytes in total.' . PHP_EOL, $data);

Start the event loop

Output should now be: responded with 258 bytes responded with 255766 bytes
Downloaded 256024 bytes in total.
Notice how was bigger and
completed later than,
even though it was started first.

Thank you!

André Roaldseth ― @androagithub/androa



By André Roaldseth


  • 3,706
Loading comments...

More from André Roaldseth