Introduction à ReactPHP
Event-driven, non-blocking I/O avec PHP

@JamesHemery

Hello 👋
@JamesHemery
🫣 James Hemery
👨💻 CTO @ Yield Studio
🫶 Amoureux des chats, de PHP,
Laravel et Javascript, ReactJS






De l'asynchrone en PHP ?
@JamesHemery
- Langage synchrone
- Principalement connu pour un cycle requête / réponse
- PHP est lent ?
Mais... Aujourd'hui on peut utiliser PHP pour bien plus qu'un cycle req/res :
- API
- Live streaming
- Console application

Sync. vs Async.
@JamesHemery
Synchronous
Asynchronous
Query 1

Traitement
Query 2

Traitement

Query 1

Traitement
Query 2

Traitement
Temps

L'idée derrière ReactPHP
@JamesHemery
- Les calculs CPU sont rapides
- Les opérations I/O sont lentes
- Travailler avec des opérations I/O non bloquantes
Opérations bloquantes:
- Filesystem
- Base de données (MySQL, Redis..)
- API tiers (REST, SOAP, Graphql...)
- Librairies tierces
- Beaucoup des fonctions natives
ReactPHP, kesako ?


@JamesHemery
- PHP Asynchrone & I/O non bloquante
- Ensemble de composants
- Reactor Pattern (comme Node.js)
- Pure PHP
- Production ready & LTS
Notre programme

@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
Event Loop

@JamesHemery
- Cœur d'une application asynchrone
- Composant bas niveau
- Ne pas confondre asynchrone et parallèle
Event Loop

@JamesHemery
Timers
- unique
- périodique
- ticks (unité de temps sur laquelle les timers se basent)
Streams event
- lecture
- écriture
Signals
Timers

@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 '...';
});Récupérer l'event loop

@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);
}
});Stream

@JamesHemery
<?php
use React\EventLoop\Loop;
Loop::addReadStream(STDIN, function ($stream) {
echo 'Vous avez dit: ' . fread($stream, 1024);
});
// Vous pouvez aussi utiliser Loop::addWriteStreamSignals

@JamesHemery
<?php
use React\EventLoop\Loop;
Loop::addSignal(SIGINT, $func = function () use (&$func) {
echo 'Au revoir !';
Loop::removeSignal(SIGINT, $func);
});
Opérations bloquantes

@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) {
//
}Opérations bloquantes

@JamesHemery

Streams

@JamesHemery
Permets de traiter un grand nombre de données au travers de chunks
Il existe trois types :
- Readable (Ex: STDIN)
- Writable (Ex: STDOUT)
- Duplex (Ex: Socket)
Lecture / Écriture

@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);
});Composite

@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);
});Pipe

@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);Exemple : Lecture de fichier

@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();
});
Promises

@JamesHemery
Permets de réagir à une opération asynchrone.
Implémenter selon la spécification Promise/A.
Une promise à trois états possible:
- Pending
- Fulfilled
- Rejected
Promise

@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;Exemple : 30 calls HTTP

@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;
});
}
Socket

@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...)
Créer un serveur socket

@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);
});
}
);Un tchat ?

@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);
}
});
}
);Un serveur HTTP ?

@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);
});
});
Envoyer des données directement au navigateur ?

@JamesHemery
🔥 Envie d'en savoir plus ?

@JamesHemery
⚡️ Ils utilisent ReactPHP

@JamesHemery
Merci !
Des questions ?

@JamesHemery
Introduction à ReactPHP
By jameshemery
Introduction à ReactPHP
- 98