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?
Github/Twitter: @WyriHaximus/@WyriHaximus
Rate my talk please: https://joind.in/talk/3a76e
Slides: https://slides.com/wyrihaximus/getting-started-with-reactphp-cakfest-2016
Code: https://github.com/WyriHaximus/cakephp-reactphp-cakefest2016
Blog: https://blog.wyrihaximus.net/categories/reactphp-series/
Getting started with ReactPHP (Cakefest 2016)
By wyrihaximus
Getting started with ReactPHP (Cakefest 2016)
- 4,627