Event-driven, non-blocking I/O avec PHP
@JamesHemery
@JamesHemery
🫣 James Hemery
👨💻 CTO @ Yield Studio
🫶 Amoureux des chats, de PHP,
Laravel et Javascript, ReactJS
@JamesHemery
Mais... Aujourd'hui on peut utiliser PHP pour bien plus qu'un cycle req/res :
@JamesHemery
Synchronous
Asynchronous
Query 1
Traitement
Query 2
Traitement
Query 1
Traitement
Query 2
Traitement
Temps
@JamesHemery
Opérations bloquantes:
@JamesHemery
@JamesHemery
Event Loop
La boucle d'événement de ReactPHP basé sur le pattern Reactor, que les bibliothèques peuvent utiliser pour les I/O.
Stream
Lecture et écriture de flux pilotés par des évènements pour des I/O non bloquantes.
Promise
Socket
Client et serveur socket TCP/IP et TLS, plaintext et asynchrones pour ReactPHP
@JamesHemery
@JamesHemery
Timers
Streams event
Signals
@JamesHemery
<?php
use React\EventLoop\Loop;
// Ne s'executera qu'une fois
Loop::addTimer(1, function () {
echo 'Hello AFUP Tours' . PHP_EOL;
});
// S'execute toutes les x secondes
$i = 0;
Loop::addPeriodicTimer(2, function ($timer) use (&$i) {
echo 'Alors, comment ça va ?' . PHP_EOL;
if ($i++ === 2) {
Loop::cancelTimer($timer);
}
});
// Basé sur les ticks
Loop::futureTick(function(){
echo '...';
});@JamesHemery
<?php
use React\EventLoop\Loop;
$loop = Loop::get(); // Factory::create();
$loop->addTimer(1, function () {
echo 'Hello AFUP Tours' . PHP_EOL;
});
$i = 0;
$loop->addPeriodicTimer(2, function ($timer) use ($loop, &$i) {
echo 'Alors, comment ça va ?' . PHP_EOL;
if ($i++ === 2) {
$loop->cancelTimer($timer);
}
});@JamesHemery
<?php
use React\EventLoop\Loop;
Loop::addReadStream(STDIN, function ($stream) {
echo 'Vous avez dit: ' . fread($stream, 1024);
});
// Vous pouvez aussi utiliser Loop::addWriteStream@JamesHemery
<?php
use React\EventLoop\Loop;
Loop::addSignal(SIGINT, $func = function () use (&$func) {
echo 'Au revoir !';
Loop::removeSignal(SIGINT, $func);
});
@JamesHemery
⚠️ Attention : l'event loop est démarré en permanence cependant, les longues opérations bloquantes bloquent la loop.
<?php
use React\EventLoop\Loop;
Loop::addTimer(1, function () {
echo 'Après 1 seconde...';
});
while (true) {
//
}@JamesHemery
@JamesHemery
Permets de traiter un grand nombre de données au travers de chunks
Il existe trois types :
@JamesHemery
<?php
use React\EventLoop\Loop;
use React\Stream\ReadableResourceStream;
use React\Stream\WritableResourceStream;
$loop = Loop::get();
$out = new WritableResourceStream(STDOUT, $loop);
$in = new ReadableResourceStream(STDIN, $loop);
$in->on('data', function ($data) use ($out) {
$out->write('Vous avez dit : ' . $data);
});@JamesHemery
<?php
use React\EventLoop\Loop;
use React\Stream\CompositeStream;
use React\Stream\ReadableResourceStream;
use React\Stream\WritableResourceStream;
$loop = Loop::get();
$out = new WritableResourceStream(STDOUT, $loop);
$in = new ReadableResourceStream(STDIN, $loop);
$composite = new CompositeStream($in, $out);
$composite->on('data', function($data) use ($composite) {
$composite->write('Vous avez dit : ' . $data);
});@JamesHemery
<?php
use React\EventLoop\Loop;
use React\Stream\ReadableResourceStream;
use React\Stream\ThroughStream;
use React\Stream\WritableResourceStream;
$loop = Loop::get();
$out = new WritableResourceStream(STDOUT, $loop);
$in = new ReadableResourceStream(STDIN, $loop);
$uppercase = new ThroughStream(function($data){
return strtoupper($data);
});
$in->pipe($uppercase)->pipe($out);@JamesHemery
<?php
use React\EventLoop\Loop;
use React\Stream\ReadableResourceStream;
use React\Stream\WritableResourceStream;
$loop = Loop::get();
$readable = new ReadableResourceStream(fopen('file.txt', 'r'), $loop, 100);
$output = new WritableResourceStream(STDOUT, $loop);
$readable->on('data', function($data) use ($output){
$output->write('Chunk : ' . $data . PHP_EOL);
});
$readable->on('end', function() use ($output) {
$output->write('Fin');
$output->end();
});
@JamesHemery
Permets de réagir à une opération asynchrone.
Implémenter selon la spécification Promise/A.
Une promise à trois états possible:
@JamesHemery
<?php
use React\Promise\Deferred;
use function React\Promise\Timer\sleep;
asynchronousFn()->then(
function ($value) {
echo $value . PHP_EOL;
},
function ($reason) {
echo 'Erreur : ' . $reason . PHP_EOL;
}
);
echo 'Autre chose...' . PHP_EOL;@JamesHemery
<?php
// L'exemple classique
for ($i = 0; $i < 30; $i++) {
$body = file_get_contents('https://catfact.ninja/fact');
$data = json_decode($body, true);
echo $data['fact'] . PHP_EOL;
}
// Avec ReactPHP
use Psr\Http\Message\ResponseInterface;
use React\Http\Browser;
$browser = new Browser();
for ($i = 0; $i < 30; $i++) {
$browser->get('https://catfact.ninja/fact')->then(function(ResponseInterface $response){
$data = json_decode($response->getBody(), true);
echo $data['fact'] . PHP_EOL;
});
}
@JamesHemery
Client et serveur socket TCP/IP et TLS, plaintext et asynchrones pour ReactPHP.
Permets d'implémenter des protocoles réseaux (HTTP par exemple) et de se connecter à d'autres réseaux (HTTP, Base de données...)
@JamesHemery
<?php
use React\EventLoop\Loop;
use React\Socket\ConnectionInterface;
use React\Socket\SocketServer;
use React\Stream\WritableResourceStream;
$loop = Loop::get();
$socket = new SocketServer('127.0.0.1:8010');
$out = new WritableResourceStream(STDOUT, $loop);
$socket->on(
'connection',
function (ConnectionInterface $connection) use ($out) {
$connection->on('data', function ($data) use ($connection, $out) {
$out->write('Nouveau message : ' . $data);
});
}
);@JamesHemery
<?php
use React\Socket\ConnectionInterface;
use React\Socket\LimitingServer;
use React\Socket\SocketServer;
$socket = new SocketServer('127.0.0.1:8010');
$socket = new LimitingServer($socket, null);
$socket->on(
'connection',
function (ConnectionInterface $connection) use ($socket) {
$connection->on('data', function ($data) use ($connection, $socket) {
if ($data === '') {
return;
}
$data = $connection->getLocalAddress() . ' à dit : ' . $data;
foreach ($socket->getConnections() as $c) {
$c->write($data . PHP_EOL);
}
});
}
);@JamesHemery
<?php
use React\Socket\ConnectionInterface;
use React\Socket\SocketServer;
$http = new SocketServer('127.0.0.1:8080');
$http->on('connection', function (ConnectionInterface $connection) {
$connection->once('data', function () use ($connection) {
$body = '<h1>Hello AFUP Tours</h1>';
$connection->end("HTTP/1.1 200 OK\r\nContent-Length: " . strlen($body) . "\r\nConnection: close\r\n\r\n" . $body);
});
});
@JamesHemery
@JamesHemery
@JamesHemery
Des questions ?
@JamesHemery