Usando React

React es una biblioteca que hace posible la programación controlada por eventos en PHP, al igual que JavaScript. Basado en el patrón del reactor, esencialmente actúa como un bucle de eventos, permitiendo que otras bibliotecas de terceros que usan sus componentes escriban código asincrónico.

La página en https://en.wikipedia.org/wiki/Reactor_patern, el patrón de diseño del reactor es un patrón de manejo de eventos para manejar solicitudes de servicio entregadas simultáneamente a un manejador de servicio por una o más entradas.
La biblioteca está disponible en https://github.com/reactphp/react

Instalando React

La biblioteca React está disponible como paquete Composer react/react. Suponiendo que todavía estamos en nuestro directorio de proyectos donde instalamos RxPHP, simplemente podemos ejecutar el siguiente comando para agregar React a nuestro proyecto:

composer require react/react

Esto debería darnos una salida similar a la siguiente:

Podemos ver bastantes paquetes interesantes de react/* siendo introducidos, react/eventloop es uno de ellos. Los mensajes que sugieren que deberíamos instalar una de las implementaciones de bucle más efectivas son definitivamente dignos de interés.

React evento de bucle

Sin ninguna de las extensiones de bucle de eventos sugeridas, el bucle de eventos React se establece de manera predeterminada en la clase React\EventLoop\StreamSelectLoop, que es un bucle de eventos basado en la función stream_select().
La página en http://php.net/manual/en/function.stream-select.php afirma, la función stream_select() acepta matrices de secuencias y espera a que cambien de estado. Como ya vimos en nuestros ejemplos anteriores, hacer un bucle de eventos en React es simple:

<?php
require_once DIR . '/vendor/autoload.php';
use \React\EventLoop\Factory;
use \Rx\Scheduler;
$loop = Factory::create();
Scheduler::setDefaultFactory(function () use ($loop) {
return new Scheduler\EventLoopScheduler($loop);
});
// Within the loop
$loop->run();

Estamos utilizando la función estática Factory::create(), que se implementa de la siguiente manera:

class Factory
{
public static function create()
{
if (function_exists('event_base_new')) {
return new LibEventLoop();
} elseif (class_exists('libev\EventLoop', false)) {
return new LibEvLoop;
} elseif (class_exists('EventBase', false)) {
return new ExtEventLoop;
}
return new StreamSelectLoop();
}
}

Aquí, podemos ver que a menos que tengamos ext-libevent, ext-event o ext-libev instalados, entonces se usa la implementación StreamSelectLoop.
Cada iteración del bucle es una marca. El bucle de eventos rastrea temporizadores y transmisiones. Sin ninguno de estos dos, no hay ticks.

<?php
require_once DIR . '/vendor/autoload.php';
use \React\EventLoop\Factory;
use \Rx\Scheduler;
echo 'STEP#1 ', time(), PHP_EOL;
$loop = Factory::create();
Scheduler::setDefaultFactory(function () use ($loop) {
return new Scheduler\EventLoopScheduler($loop);
});
echo 'STEP#2 ', time(), PHP_EOL;
$loop->run();
echo 'STEP#3 ', time(), PHP_EOL;

El código anterior nos da el siguiente resultado:

Tan pronto como agreguemos algunos temporizadores, la situación cambia

<?php
require_once DIR . '/vendor/autoload.php';
use \React\EventLoop\Factory;
use \Rx\Scheduler;
echo 'STEP#1 ', time(), PHP_EOL;
$loop = Factory::create();
Scheduler::setDefaultFactory(function () use ($loop) {
return new Scheduler\EventLoopScheduler($loop);
});
echo 'STEP#2 ', PHP_EOL;
$loop->addTimer(2, function () {
echo 'timer#1 ', time(), PHP_EOL;
});
echo 'STEP#3 ', time(), PHP_EOL;
$loop->addTimer(5, function () {
echo 'timer#2 ', time(), PHP_EOL;
});
echo 'STEP#4 ', time(), PHP_EOL;
$loop->addTimer(3, function () {
echo 'timer#3 ', time(), PHP_EOL;
});
echo 'STEP#5 ', time(), PHP_EOL;
$loop->run();
echo 'STEP#6 ', time(), PHP_EOL;

El código anterior nos da el siguiente resultado:

Observe el orden de salida del temporizador y el tiempo al lado de cada uno. Nuestro ciclo aún logró terminar, ya que nuestros temporizadores expiraron. Para mantener el ciclo funcionando constantemente, podemos agregar un temporizador periódico

<?php
require_once DIR . '/vendor/autoload.php';
use \React\EventLoop\Factory;
use \Rx\Scheduler;
echo 'STEP#1 ', time(), PHP_EOL;
$loop = Factory::create();
Scheduler::setDefaultFactory(function () use ($loop) {
return new Scheduler\EventLoopScheduler($loop);
});
echo 'STEP#2 ', PHP_EOL;
$loop->addPeriodicTimer(1, function () {
echo 'timer ', time(), PHP_EOL;
});
echo 'STEP#3 ', time(), PHP_EOL;
$loop->run();
echo 'STEP#4 ', time(), PHP_EOL;

El código anterior nos da el siguiente resultado:

Este ciclo ahora continuará produciendo el mismo temporizador … salida hasta que presionemos Ctrl + C en la consola. Podríamos estar preguntándonos, ¿cómo difiere esto de un PHP while loop? En general, el bucle while es de tipo sondeo, ya que verifica continuamente las cosas, dejando poco o ningún espacio para que el procesador cambie de tarea. El bucle de eventos usa I/O impulsadas por interrupciones más eficientes en lugar de sondeo. Sin embargo, el StreamSelectLoop predeterminado utiliza el bucle while para su implementación de bucle de eventos.
La adición de temporizadores y flujos es lo que lo hace útil, ya que nos extrae los bits duros.

Observables y bucle de eventos

Avancemos y veamos cómo podemos hacer que nuestros observables funcionen con un bucle de eventos:

<?php
require_once DIR . '/vendor/autoload.php';
use \React\EventLoop\Factory;
use \Rx\Scheduler;
use \Rx\Observable;
use \Rx\Subject\Subject;
use \Rx\Scheduler\EventLoopScheduler;
$loop = Factory::create();
Scheduler::setDefaultFactory(function () use ($loop) {
return new Scheduler\EventLoopScheduler($loop);
});
$stdin = fopen('php://stdin', 'r');
stream_set_blocking($stdin, 0);
$observer = new class() extends Subject
{
public function onCompleted()
{
echo '$observer.onCompleted: ', PHP_EOL;
parent::onCompleted();
}
public function onNext($val)
{
echo '$observer.onNext: ', $val, PHP_EOL;
parent::onNext($val);
}
public function onError(\Throwable $error)
{
echo '$observer.onError: ', $error->getMessage(), PHP_EOL;
parent::onError($error);
}
};
$loop = Factory::create();
$scheduler = new EventLoopScheduler($loop);
$disposable = Observable::interval(500, $scheduler)
->map(function () use ($stdin) {
return trim(fread($stdin, 1024));
})
->filter(function ($str) {
return strlen($str) > 0;
})
->subscribe($observer);
$observer->filter(function ($value) {
return $value == 'quit';
})->subscribeCallback(function ($value) use ($disposable) {
echo 'disposed!', PHP_EOL;
$disposable->dispose();
});
$loop->run();

Están sucediendo muchas cosas aquí. Comenzamos creando una entrada estándar y luego la marcamos como no bloqueante. Luego creamos el observador del tipo Subject. Esto se debe a que, como veremos más adelante, queremos que nuestro observador se comporte como observador y observable.
Luego instanciamos el bucle y pasamos a EventLoopScheduler. Para que los observables funcionen con el bucle, necesitamos envolverlos con un programador. Luego usamos la instancia de IntervalObservable, haciendo que su operador map() lea la entrada estándar, mientras que el operador filter() se configuró para filtrar las entradas vacías (presionando la tecla Enter en consola sin texto). Almacenamos este observable en una variable $desechable. Finalmente, dado que nuestro $observador era una instancia de Subject, pudimos adjuntarle el operador filter() así como subscribeCallback(). Le indicamos al operador filter() que solo filtre la entrada con la cadena de salida. Una vez que se dejó de escribir en la consola, seguido de la tecla Intro, se ejecutó subscribeCallback().
Dentro de subscribeCallback(), tenemos una expresión $disposable->dispose(). Al llamar al método de disposición del desechable, se anula automáticamente la suscripción de $observer desde $observable. Dado que no había otros temporizadores o transmisiones dentro del bucle, esto terminó automáticamente el bucle.
La siguiente captura de pantalla muestra el resultado de la consola del código anterior:

Cuando se ejecutó el código, primero vimos la cadena de inicio, luego escribimos John y presionamos, luego decimos $observer.onNext …, que se repitió todo el tiempo hasta que escribimos quit.
El bucle de eventos React abre una posibilidad interesante para nosotros, al igual que estamos acostumbrados a ver en JavaScript y en el navegador. Si bien hay mucho más que decir sobre React, esto debería ser suficiente para comenzar con la combinación de RxPHP y React.

Comparte