Getting started with ReactPHP

    Cees-Jan Kiewiet

  • @WyriHaximus
  • ReactPHP Team member
  • Sculpin Team member
  • Lead Software Engineer at 

 

 

What is ReactPHP

Building a sample app:

  • Analyzes hostnames
  • Looks up IP address
  • Fetches page title
  • Fetches GeoIP

Demo time!

Requirements

  • cakephp:^3.0
  • react/event-loop
  • react/dns
  • react/http
  • react/filesystem
  • wyrihaximus/react-guzzle-psr7
  • clue/sse-react
  • reactjs

https://github.com/WyriHaximus/cakephp-reactphp-cakefest2016

The Event Loop

The Heart of asynchronous code

Streams
Timers
Ticks

Loop creation

<?php

use React\EventLoop\Factory;

require 'vendor/autoload.php';

$loop = Factory::create();

$loop->run();

Available loops

  • StreamSelectLoop
  • LibEventLoop
  • LibEvLoop
  • ExtEventLoop

HTTP Server

Listens for incoming requests

Requests

Not
Connections

Creating the HTTP Server

$socket = new SocketServer($loop);
$http = new HttpServer($socket, $loop);
$http->on('request', function (Request $request, Response $response) {
    EventManager::instance()->dispatch(RequestEvent::create($request, $response));
});
$socket->listen(1337);

SSE listener

public function request(RequestEvent $event)
{
    if ($event->getRequest()->getPath() !== '/sse') {
        return;
    }

    $event->getResponse()->writeHead(
        200,
        ['Content-Type' => 'text/event-stream')
    );
    $this->channel->connect($event->getResponse());
    $event->getResponse()->on('close', function () use ($event) {
        $this->channel->disconnect($event->getResponse());
    });
    $event->stopPropagation();
}

Broadcast listener

public function broadcast(BroadcastEvent $event)
{
    $this->channel->writeMessage(json_encode($event->data()));
}

Lookup listener

public function request(RequestEvent $event)
{
    if ($event->getRequest()->getPath() == '/lookup.json') {
        EventManager::instance()->dispatch(
            LookupEvent::create($event->getRequest()->getQuery()['host'])
        );
        $event->getResponse()->writeHead(200);
        $event->getResponse()->end('{}');
        $event->stopPropagation();
    }
}

Filesystem

Evented filesystem access

Promises
Streams
Threads

Set up

public function __construct(LoopInterface $loop)
{
    $this->files = Filesystem::create($loop)
        ->dir(ROOT . DS . App::path('webroot', 'WyriHaximus/CakeFest2016')[0])
        ->lsRecursive();
}

Request listener

public function request(RequestEvent $event)
{
    $this->files->then(function (SplObjectStorage $listing) use ($event) {
        foreach ($listing as $node) {
            if (!($node instanceof FileInterface)) {
                continue;
            }

            if ($event->getRequest()->getPath() == $node->getPath()) {
                $node->getContents()->then(function ($contents) use ($event) {
                    $event->getResponse()->writeHead(200);
                    $event->getResponse()->end($contents);
                });
                $event->stopPropagation();
            }
        }
    });
}

DNS Resolver

Resolves DNS records by hostname

Hostnames
Cache
Promises

Creating the resolver

<?php

use React\Dns\Resolver\Factory as ResolverFactory;

$resolver = (new ResolverFactory())->createCached('8.8.8.8', $loop);

Looking up the IP

public function lookup(LookupEvent $event)
{
    $this->resolver->resolve($event->getHostname())->then(function ($ip) {
        EventManager::instance()->dispatch(BroadcastEvent::create([
            'type' => 'ip',
            'data' => $ip,
        ]));
    });
}

HTTP Client

Basic HTTP 1.0 client

Streaming
Responses
Explicit

Setting up the HTTP client

use GuzzleHttp\Client;
use WyriHaximus\React\GuzzlePsr7\HttpClientAdapter;

$httpClient = new Client([
    'handler' => new HttpClientAdapter($loop, null, $dns),
]);

GeoIP lookup

public function lookup(LookupEvent $event)
{
    $this->httpClient->getAsync(
        'https://freegeoip.net/json/' . $event->getHostname()
    )->then(function (ResponseInterface $response) {
        EventManager::instance()->dispatch(BroadcastEvent::create([
            'type' => 'geoip',
            'data' => json_decode($response->getBody()->getContents()),
        ]));
    });
}

Title lookup


public function request(LookupEvent $event)
{
    $this->httpClient->getAsync('http://' . $event->getHostname())
        ->then(function (ResponseInterface $response) {
        if (preg_match(
            '/<title>(.+)<\/title>/',
            $response->getBody()->getContents(),
            $matches
        ) && isset($matches[1])) {
            EventManager::instance()->dispatch(BroadcastEvent::create([
                'type' => 'title',
                'data' => $matches[1],
            ]));
            return;
        }

        EventManager::instance()->dispatch(BroadcastEvent::create([
            'type' => 'title',
            'data' => 'NO TITLE FOUND',
        ]));
    });
}

Questions?

Made with Slides.com