Usando ReactPHP para ejecutar tareas de Drupal

ReaccionarPHP es un marco PHP sin bloqueo basado en eventos que le permite trabajar en un script de ejecución prolongada a través de un bucle de eventos. En esencia, ReactPHP proporciona un bucle de eventos y utilidades para activar eventos en intervalos específicos y ejecutar su código. Esto es diferente a la ejecución normal del script PHP, que tiene un ciclo de vida corto y por solicitudes individuales.

ReactPHP se ha utilizado para crear aplicaciones de servidor web, servidores de socket web y más. Pero, ¿y si usáramos ReactPHP para ejecutar operaciones y tareas sobre una aplicación Drupal?

Técnicamente, esto podría ser factible con un conjunto de trabajos cron programados a intervalos específicos que invoquen los comandos de Drush o Drupal Console. Pero hay una limitación allí: la capacidad de manipular las entradas de trabajos cron para el usuario cada vez que se produce una implementación. Los proveedores de alojamiento como Platform.sh admiten esto, pero solo en el contenedor principal de la aplicación. Los contenedores de trabajo no admiten definiciones de trabajos cron. Además, ¿qué pasa con el manejo de errores?

Podemos combinar el bucle de eventos ReactPHP con la biblioteca ReactPHP ChildProcess para ejecutar nuestras herramientas de línea de comandos. El proceso secundario se adjunta al bucle de eventos y nos permite transmitir la salida desde STDOUT y STDERR. Esto nos permite registrar la salida del comando o manejar los errores que ocurren durante estos procesos en segundo plano.

Es más fácil crear una función que ejecute un nuevo proceso secundario, proporcione un bucle de eventos para él y vincule eventos a los flujos de salida.

function run_command(string $command): void {
  $loop = React\EventLoop\Factory::create();
  $process = new React\ChildProcess\Process($command);
  $process->start($loop);
  $process->on('exit', function ($exitCode) use ($command) {
    // Trigger alerts that the command finished.
  });
  $process->stdout->on('data', function ($chunk) {
    // Optinally log the output.
  });
  $process->stdout->on('error', function (Exception $e) use ($command) {
    // Log an error.
  });
  $process->stderr->on('data', function ($chunk) use ($command) {
    if (!empty(trim($chunk))) {
      // Log output from stderr
    }
  });
  $process->stderr->on('error', function (Exception $e) use ($command) {
    // Log an error.
  });
  $loop->run();
}

Soy un gran admirador de Rollbar y tengo registros enviados allí.

Ahora, creemos nuestro ciclo de eventos principal que ejecutará nuestros comandos en diferentes intervalos. Podemos ejecutar cron cada veinte minutos

$loop = React\EventLoop\Factory::create();
// Run cron every twenty minutes.
$loop->addPeriodicTimer(1200, function () {
  run_command('drush cron');
});
$loop->run();

Si está utilizando un sistema de colas para procesar trabajos de forma asíncrona a través del módulo de cola Avanzado, es posible que desee un procesamiento más continuo que actúe como un demonio.

$loop = React\EventLoop\Factory::create();
// Every thirty seconds, process jobs from queue1
$loop->addPeriodicTimer(30, function () {
  run_command(sprintf('drush advancedqueue:queue:process queue1'));
});
// Every two minutes, process jobs from queue2
$loop->addPeriodicTimer(120, function () {
  run_command(sprintf('drush advancedqueue:queue:process queue2'));
});
$loop->run();

Esto nos ha permitido ejecutar Drush como un proceso secundario en un ciclo de eventos de ReactPHP para ejecutar tareas. ¿Qué pasaría si realmente arrancáramos Drupal e invocamos nuestro código directamente en lugar de a través de procesos secundarios? ¡Podemos!

Para comenzar, necesitamos arrancar Drupal. Esto requiere la creación de un objeto de solicitud y una ruta simulada. los DrupalKernel y otros componentes se acoplan a la solicitud que contiene metainformación sobre una ruta. Afortunadamente, Drupal admite una <none> ruta.

Requerimos el cargador automático y creamos nuestro objeto de solicitud. Por lo general, tengo mis scripts PHP en un directorio de scripts en la raíz de mi proyecto, por lo que mi cargador automático está en ../vendor/autoload.php.

$autoloader = require __DIR__ . '/../vendor/autoload.php';

$request = Symfony\Component\HttpFoundation\Request::createFromGlobals();
$request->attributes->set(
    Symfony\Cmf\Component\Routing\RouteObjectInterface::ROUTE_OBJECT,
    new Symfony\Component\Routing\Route('<none>')
);
$request->attributes->set(
    Symfony\Cmf\Component\Routing\RouteObjectInterface::ROUTE_NAME,
     '<none>'
);

A continuación, arrancamos el DrupalKernel. Necesitamos ejecutar el bootEnvironment método, esto configura cierta información necesaria para Drupal. A continuación, debemos especificar la ruta del sitio que contiene la configuración.php que queremos usar; generalmente, es sites/default. Luego simplemente iniciamos el kernel y ejecutamos el control previo de la solicitud para que todo esté en funcionamiento.

$kernel = new Drupal\Core\DrupalKernel('prod', $autoloader);
$kernel::bootEnvironment();
$kernel->setSitePath('sites/default');
Drupal\Core\Site\Settings::initialize($kernel->getAppRoot(), $kernel->getSitePath(), $autoloader);
$kernel->boot();
$kernel->preHandle($request);

Ahora podemos hacer que nuestro bucle de eventos marque y ejecute nuestro código según sea necesario.

$loop = React\EventLoop\Factory::create();
$loop->addPeriodicTimer(10, function () {
    $cron = \Drupal::service('cron');
    $cron->run();
  });
$loop->run();

Aquí hay un enlace a una esencia de Github de los archivos completos que se muestran aquí: https://gist.github.com/mglaman/6f5b0b2194d2f5ec7f1a40d00dd5ca6c..

Similar Posts

Leave a Reply

Your email address will not be published.