ReactPHP: programación asíncrona en PHP con event loop, promesas y streams

ReactPHP introduce un modelo de I/O no bloqueante en PHP mediante un event loop. En lugar de esperar bloqueado a que un socket responda o un temporizador expire, el loop atiende miles de operaciones simultáneas con un único hilo, igual que Node.js.

Instalación

composer require react/event-loop react/promise react/http react/socket

El event loop

<?php
require 'vendor/autoload.php';

use ReactEventLoopLoop;

// Temporizador que se dispara una vez
Loop::addTimer(2.0, function () {
    echo "Han pasado 2 segundosn";
});

// Temporizador periódico
Loop::addPeriodicTimer(1.0, function () {
    echo "Tick: " . date('H:i:s') . "n";
});

echo "El loop arranca ahoran";
Loop::run();
?>

Promesas

ReactPHP usa promesas para manejar operaciones asíncronas sin bloquear:

<?php
use ReactPromiseDeferred;

function operacionAsincrona(): ReactPromisePromiseInterface
{
    $deferred = new Deferred();

    Loop::addTimer(1.0, function () use ($deferred) {
        $deferred->resolve('Resultado listo');
        // $deferred->reject(new Exception('Algo falló'));
    });

    return $deferred->promise();
}

$promesa = operacionAsincrona();
$promesa->then(
    function (string $resultado) {
        echo "Éxito: $resultadon";
    },
    function (Exception $error) {
        echo "Error: {$error->getMessage()}n";
    }
);

Loop::run();
?>

Encadenar promesas

<?php
use function ReactPromiseresolve;

resolve(10)
    ->then(fn($v) => $v * 2)        // 20
    ->then(fn($v) => $v + 5)        // 25
    ->then(fn($v) => "Resultado: $v")
    ->then(fn($s) => print($s . "n"));

Loop::run();
?>

Servidor HTTP con react/http

<?php
use ReactHttpHttpServer;
use ReactHttpMessageResponse;
use ReactSocketSocketServer;
use PsrHttpMessageServerRequestInterface;

$server = new HttpServer(function (ServerRequestInterface $request) {
    $path = $request->getUri()->getPath();

    return match ($path) {
        '/'       => Response::plaintext("Hola desde ReactPHPn"),
        '/json'   => Response::json(['ok' => true, 'ts' => time()]),
        default   => new Response(404, [], "No encontradon"),
    };
});

$socket = new SocketServer('0.0.0.0:8080');
$server->listen($socket);

echo "Servidor en http://localhost:8080n";
Loop::run();
?>

Streams no bloqueantes

<?php
use ReactSocketConnector;
use ReactSocketConnectionInterface;

$connector = new Connector();
$connector->connect('tcp://example.com:80')->then(
    function (ConnectionInterface $conn) {
        $conn->write("GET / HTTP/1.0rnHost: example.comrnrn");

        $conn->on('data', function (string $data) {
            echo substr($data, 0, 200) . "n";
        });

        $conn->on('close', function () {
            echo "Conexión cerradan";
        });
    },
    function (Exception $e) {
        echo "Error de conexión: {$e->getMessage()}n";
    }
);

Loop::run();
?>

Antipatrones a evitar

  • sleep() dentro del loop: bloquea el hilo entero. Usa Loop::addTimer() en su lugar.
  • PDO/MySQLi síncronos: las consultas bloquean el loop. Usa react/mysql para consultas no bloqueantes o ejecuta las consultas pesadas en procesos separados.
  • file_get_contents() en el loop: bloquea en I/O de disco. Usa react/filesystem o carga los ficheros antes de arrancar el loop.

Concurrencia con ReactPromiseall()

<?php
use function ReactPromiseall;

$promesas = [
    obtenerDatosUsuario(1),
    obtenerDatosProducto(42),
    obtenerPedidos(1),
];

all($promesas)->then(function (array $resultados) {
    [$usuario, $producto, $pedidos] = $resultados;
    echo "Todo listo a la vezn";
});

Loop::run();
?>

¿Cuándo usar ReactPHP?

ReactPHP brilla en servidores WebSocket, proxies, scrapers que hacen cientos de peticiones HTTP en paralelo y microservicios con I/O intensivo. No es la mejor opción para aplicaciones web tradicionales donde el coste de inicialización por petición es el cuello de botella; ahí conviene FrankenPHP o Swoole.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP