Procesos
Introducción
Laravel ofrece una API mínima y expresiva en torno al componente Process de Symfony, lo que te permite invocar de manera conveniente procesos externos desde tu aplicación Laravel. Las características de proceso de Laravel se centran en los casos de uso más comunes y brindan una experiencia de desarrollador magnífica.
Invocando Procesos
Para invocar un proceso, puedes usar los métodos run
y start
que ofrece la fachada Process
. El método run
invocará un proceso y esperará a que el proceso termine de ejecutarse, mientras que el método start
se utiliza para la ejecución de procesos de forma asincrónica. Examinaremos ambos enfoques en esta documentación. Primero, examinemos cómo invocar un proceso básico y sincrónico e inspeccionar su resultado:
use Illuminate\Support\Facades\Process; $result = Process::run('ls -la'); return $result->output();
Por supuesto, la instancia Illuminate\Contracts\Process\ProcessResult
devuelta por el método run
ofrece una variedad de métodos útiles que se pueden usar para inspeccionar el resultado del proceso:
$result = Process::run('ls -la'); $result->successful();$result->failed();$result->exitCode();$result->output();$result->errorOutput();
Lanzando Excepciones
Si tienes un resultado de proceso y te gustaría lanzar una instancia de Illuminate\Process\Exceptions\ProcessFailedException
si el código de salida es mayor que cero (lo que indica un fallo), puedes usar los métodos throw
y throwIf
. Si el proceso no falló, se devolverá la instancia del resultado del proceso:
$result = Process::run('ls -la')->throw(); $result = Process::run('ls -la')->throwIf($condition);
Opciones de Proceso
Por supuesto, es posible que necesites personalizar el comportamiento de un proceso antes de invocarlo. Afortunadamente, Laravel te permite ajustar una variedad de características del proceso, como el directorio de trabajo, el tiempo de espera y las variables de entorno.
Ruta del Directorio de Trabajo
Puedes usar el método path
para especificar el directorio de trabajo del proceso. Si este método no se invoca, el proceso heredará el directorio de trabajo del script PHP que se está ejecutando actualmente:
$result = Process::path(__DIR__)->run('ls -la');
Entrada
Puedes proporcionar la entrada a través de la "entrada estándar" del proceso utilizando el método input
:
$result = Process::input('Hello World')->run('cat');
Timeouts
Por defecto, los procesos lanzarán una instancia de Illuminate\Process\Exceptions\ProcessTimedOutException
después de ejecutar durante más de 60 segundos. Sin embargo, puedes personalizar este comportamiento a través del método timeout
:
$result = Process::timeout(120)->run('bash import.sh');
O, si deseas desactivar el tiempo de espera del proceso por completo, puedes invocar el método forever
:
$result = Process::forever()->run('bash import.sh');
El método idleTimeout
se puede utilizar para especificar el número máximo de segundos que el proceso puede ejecutarse sin devolver ninguna salida:
$result = Process::timeout(60)->idleTimeout(30)->run('bash import.sh');
Variables de Entorno
Las variables de entorno pueden ser proporcionadas al proceso a través del método env
. El proceso invocado también heredará todas las variables de entorno definidas por su sistema:
$result = Process::forever() ->env(['IMPORT_PATH' => __DIR__]) ->run('bash import.sh');
Si deseas eliminar una variable de entorno heredada del proceso invocado, puedes proporcionar esa variable de entorno con un valor de false
:
$result = Process::forever() ->env(['LOAD_PATH' => false]) ->run('bash import.sh');
Modo TTY
El método tty
se puede utilizar para habilitar el modo TTY para tu proceso. El modo TTY conecta la entrada y salida del proceso con la entrada y salida de tu programa, lo que permite que tu proceso abra un editor como Vim o Nano como un proceso:
Process::forever()->tty()->run('vim');
Salida del Proceso
Como se discutió anteriormente, la salida del proceso puede ser accedida utilizando los métodos output
(stdout) y errorOutput
(stderr) en un resultado de proceso:
use Illuminate\Support\Facades\Process; $result = Process::run('ls -la'); echo $result->output();echo $result->errorOutput();
Sin embargo, la salida también puede ser recopilada en tiempo real pasando una función anónima
como segundo argumento al método run
. La función anónima
recibirá dos argumentos: el "tipo" de salida (stdout
o stderr
) y la cadena de salida en sí:
$result = Process::run('ls -la', function (string $type, string $output) { echo $output;});
Laravel también ofrece los métodos seeInOutput
y seeInErrorOutput
, que proporcionan una forma conveniente de determinar si una cadena dada estaba contenida en la salida del proceso:
if (Process::run('ls -la')->seeInOutput('laravel')) { // ...}
Desactivando la Salida del Proceso
Si tu proceso está generando una cantidad significativa de salida que no te interesa, puedes conservar memoria deshabilitando la recuperación de salida por completo. Para lograr esto, invoca el método quietly
mientras construyes el proceso:
use Illuminate\Support\Facades\Process; $result = Process::quietly()->run('bash import.sh');
Pipelines
A veces es posible que desees que la salida de un proceso sea la entrada de otro proceso. Esto a menudo se refiere a "encadenar" la salida de un proceso en otro. El método pipe
proporcionado por las fachadas Process
facilita este logro. El método pipe
ejecutará los procesos encadenados de forma sincrónica y devolverá el resultado del proceso para el último proceso en la secuencia:
use Illuminate\Process\Pipe;use Illuminate\Support\Facades\Process; $result = Process::pipe(function (Pipe $pipe) { $pipe->command('cat example.txt'); $pipe->command('grep -i "laravel"');}); if ($result->successful()) { // ...}
Si no necesitas personalizar los procesos individuales que componen la tubería, simplemente puedes pasar un array de cadenas de comandos al método pipe
:
$result = Process::pipe([ 'cat example.txt', 'grep -i "laravel"',]);
La salida del proceso puede ser recopilada en tiempo real pasando una función anónima
como segundo argumento al método pipe
. La función anónima
recibirá dos argumentos: el "tipo" de salida (stdout
o stderr
) y la cadena de salida en sí:
$result = Process::pipe(function (Pipe $pipe) { $pipe->command('cat example.txt'); $pipe->command('grep -i "laravel"');}, function (string $type, string $output) { echo $output;});
Laravel también te permite asignar claves de cadena a cada proceso dentro de un pipeline a través del método as
. Esta clave también se pasará a la función anónima
de salida proporcionada al método pipe
, lo que te permitirá determinar a qué proceso pertenece la salida:
$result = Process::pipe(function (Pipe $pipe) { $pipe->as('first')->command('cat example.txt'); $pipe->as('second')->command('grep -i "laravel"');})->start(function (string $type, string $output, string $key) { // ...});
Procesos Asíncronos
Mientras que el método run
invoca procesos de forma sincrónica, el método start
se puede utilizar para invocar un proceso de manera asincrónica. Esto permite que su aplicación continúe realizando otras tareas mientras el proceso se ejecuta en segundo plano. Una vez que se haya invocado el proceso, puede utilizar el método running
para determinar si el proceso sigue en ejecución:
$process = Process::timeout(120)->start('bash import.sh'); while ($process->running()) { // ...} $result = $process->wait();
Como habrás notado, puedes invocar el método wait
para esperar hasta que el proceso haya terminado de ejecutarse y recuperar la instancia del resultado del proceso:
$process = Process::timeout(120)->start('bash import.sh'); // ... $result = $process->wait();
IDs de proceso y señales
El método id
se puede usar para recuperar el ID de proceso asignado por el sistema operativo al proceso en ejecución:
$process = Process::start('bash import.sh'); return $process->id();
Puedes usar el método signal
para enviar una "señal" al proceso en ejecución. Una lista de constantes de señal predefinidas se puede encontrar dentro de la documentación de PHP:
$process->signal(SIGUSR2);
Salida de Proceso Asincrónico
Mientras se está ejecutando un proceso asíncrono, puedes acceder a toda su salida actual utilizando los métodos output
y errorOutput
; sin embargo, puedes utilizar latestOutput
y latestErrorOutput
para acceder a la salida del proceso que ha ocurrido desde la última vez que se recuperó la salida:
$process = Process::timeout(120)->start('bash import.sh'); while ($process->running()) { echo $process->latestOutput(); echo $process->latestErrorOutput(); sleep(1);}
Al igual que el método run
, la salida también puede recopilarse en tiempo real a partir de procesos asincrónicos pasando una función anónima como segundo argumento al método start
. La función anónima recibirá dos argumentos: el "tipo" de salida (stdout
o stderr
) y la cadena de salida en sí:
$process = Process::start('bash import.sh', function (string $type, string $output) { echo $output;}); $result = $process->wait();
Procesos Concurrentes
Laravel también facilita la gestión de un grupo de procesos asíncronos y concurrentes, lo que te permite ejecutar muchas tareas de forma simultánea. Para empezar, invoca el método pool
, que acepta una función anónima que recibe una instancia de Illuminate\Process\Pool
.
Dentro de esta función anónima
, puedes definir los procesos que pertenecen al grupo. Una vez que se inicia un grupo de procesos a través del método start
, puedes acceder a la colección de procesos en ejecución a través del método running
:
use Illuminate\Process\Pool;use Illuminate\Support\Facades\Process; $pool = Process::pool(function (Pool $pool) { $pool->path(__DIR__)->command('bash import-1.sh'); $pool->path(__DIR__)->command('bash import-2.sh'); $pool->path(__DIR__)->command('bash import-3.sh');})->start(function (string $type, string $output, int $key) { // ...}); while ($pool->running()->isNotEmpty()) { // ...} $results = $pool->wait();
Como puedes ver, puedes esperar a que todos los procesos del pool terminen de ejecutarse y resuelvan sus resultados a través del método wait
. El método wait
devuelve un objeto accesible como un array que te permite acceder a la instancia del resultado del proceso de cada proceso en el pool por su clave:
$results = $pool->wait(); echo $results[0]->output();
O, para mayor comodidad, se puede utilizar el método concurrently
para iniciar un pool de procesos asíncronos y esperar inmediatamente sus resultados. Esto puede proporcionar una sintaxis particularmente expresiva cuando se combina con las capacidades de desestructuración de arrays de PHP:
[$first, $second, $third] = Process::concurrently(function (Pool $pool) { $pool->path(__DIR__)->command('ls -la'); $pool->path(app_path())->command('ls -la'); $pool->path(storage_path())->command('ls -la');}); echo $first->output();
Nombrar Procesos del Pool
Acceder a los resultados del pool de procesos a través de una clave numérica no es muy expresivo; por lo tanto, Laravel te permite asignar claves de cadena a cada proceso dentro de un pool a través del método as
. Esta clave también se pasará a la función anónima proporcionada al método start
, lo que te permitirá determinar a qué proceso pertenece la salida:
$pool = Process::pool(function (Pool $pool) { $pool->as('first')->command('bash import-1.sh'); $pool->as('second')->command('bash import-2.sh'); $pool->as('third')->command('bash import-3.sh');})->start(function (string $type, string $output, string $key) { // ...}); $results = $pool->wait(); return $results['first']->output();
Identificadores de Proceso de Pool y Señales
Dado que el método running
del pool de procesos proporciona una colección de todos los procesos invocados dentro del pool, puedes acceder fácilmente a los IDs de proceso subyacentes del pool:
$processIds = $pool->running()->each->id();
Y, para mayor comodidad, puedes invocar el método signal
en un pool de procesos para enviar una señal a cada proceso dentro del pool:
$pool->signal(SIGUSR2);
Pruebas
Muchos servicios de Laravel ofrecen funcionalidades para ayudarte a escribir pruebas de manera fácil y expresiva, y el servicio de procesos de Laravel no es una excepción. El método fake
de la fachada Process
te permite instruir a Laravel para que devuelva resultados simulados / dummy cuando se invocan procesos.
Simulación de Procesos
Para explorar la capacidad de Laravel para simular procesos, imaginemos una ruta que invoca un proceso:
use Illuminate\Support\Facades\Process;use Illuminate\Support\Facades\Route; Route::get('/import', function () { Process::run('bash import.sh'); return 'Import complete!';});
Al probar esta ruta, podemos instruir a Laravel para que devuelva un resultado de proceso exitoso simulado para cada proceso invocado llamando al método fake
en la fachada Process
sin argumentos. Además, incluso podemos afirmar que un proceso dado fue "ejecutado":
<?php use Illuminate\Process\PendingProcess;use Illuminate\Contracts\Process\ProcessResult;use Illuminate\Support\Facades\Process; test('process is invoked', function () { Process::fake(); $response = $this->get('/import'); // Simple process assertion... Process::assertRan('bash import.sh'); // Or, inspecting the process configuration... Process::assertRan(function (PendingProcess $process, ProcessResult $result) { return $process->command === 'bash import.sh' && $process->timeout === 60; });});
<?php namespace Tests\Feature; use Illuminate\Process\PendingProcess;use Illuminate\Contracts\Process\ProcessResult;use Illuminate\Support\Facades\Process;use Tests\TestCase; class ExampleTest extends TestCase{ public function test_process_is_invoked(): void { Process::fake(); $response = $this->get('/import'); // Simple process assertion... Process::assertRan('bash import.sh'); // Or, inspecting the process configuration... Process::assertRan(function (PendingProcess $process, ProcessResult $result) { return $process->command === 'bash import.sh' && $process->timeout === 60; }); }}
Como se discutió, invocar el método fake
en la fachada Process
instruirá a Laravel a devolver siempre un resultado de proceso exitoso sin salida. Sin embargo, puedes especificar fácilmente la salida y el código de salida para los procesos simulados utilizando el método result
de la fachada Process
:
Process::fake([ '*' => Process::result( output: 'Test output', errorOutput: 'Test error output', exitCode: 1, ),]);
Fingiendo Procesos Específicos
Como habrás notado en un ejemplo anterior, la facade Process
te permite especificar diferentes resultados simulados por proceso pasando un array al método fake
.
Las claves del array deben representar patrones de comando que deseas simular y sus resultados asociados. Se puede usar el carácter *
como un carácter comodín. Cualquier comando de proceso que no haya sido simulado será invocado realmente. Puedes usar el método result
de la fachada Process
para construir resultados simulados / falsos para estos comandos:
Process::fake([ 'cat *' => Process::result( output: 'Test "cat" output', ), 'ls *' => Process::result( output: 'Test "ls" output', ),]);
Si no necesitas personalizar el código de salida o la salida de error de un proceso simulado, puede que te resulte más conveniente especificar los resultados del proceso simulado como cadenas simples:
Process::fake([ 'cat *' => 'Test "cat" output', 'ls *' => 'Test "ls" output',]);
Falsificando Secuencias de Proceso
Si el código que estás probando invoca múltiples procesos con el mismo comando, es posible que desees asignar un resultado de proceso simulado diferente a cada invocación del proceso. Puedes lograr esto a través del método sequence
de la fachada Process
:
Process::fake([ 'ls *' => Process::sequence() ->push(Process::result('First invocation')) ->push(Process::result('Second invocation')),]);
Simulando Ciclos de Vida de Procesos Asincrónicos
Hasta ahora, hemos discutido principalmente los procesos simulados que se invocan de forma sincrónica utilizando el método run
. Sin embargo, si estás intentando probar código que interactúa con procesos asincrónicos invocados a través de start
, es posible que necesites un enfoque más sofisticado para describir tus procesos simulados.
Por ejemplo, imaginemos la siguiente ruta que interactúa con un proceso asíncrono:
use Illuminate\Support\Facades\Log;use Illuminate\Support\Facades\Route; Route::get('/import', function () { $process = Process::start('bash import.sh'); while ($process->running()) { Log::info($process->latestOutput()); Log::info($process->latestErrorOutput()); } return 'Done';});
Para simular este proceso correctamente, necesitamos poder describir cuántas veces debería devolver true
el método running
. Además, es posible que queramos especificar múltiples líneas de salida que se deben devolver en secuencia. Para lograr esto, podemos usar el método describe
de la fachada Process
:
Process::fake([ 'bash import.sh' => Process::describe() ->output('First line of standard output') ->errorOutput('First line of error output') ->output('Second line of standard output') ->exitCode(0) ->iterations(3),]);
Vamos a profundizar en el ejemplo anterior. Usando los métodos output
y errorOutput
, podemos especificar múltiples líneas de salida que se devolverán en secuencia. El método exitCode
puede usarse para especificar el código de salida final del proceso simulado. Finalmente, el método iterations
puede usarse para especificar cuántas veces el método running
debería devolver true
.
Afirmaciones Disponibles
Como se discutió anteriormente, Laravel ofrece varias afirmaciones de proceso para tus pruebas de características. Discutiremos cada una de estas afirmaciones a continuación.
assertRan
Afirma que un proceso dado fue invocado:
use Illuminate\Support\Facades\Process; Process::assertRan('ls -la');
El método assertRan
también acepta una función anónima, que recibirá una instancia de un proceso y un resultado del proceso, lo que te permitirá inspeccionar las opciones configuradas del proceso. Si esta función anónima devuelve true
, la afirmación "pasará":
Process::assertRan(fn ($process, $result) => $process->command === 'ls -la' && $process->path === __DIR__ && $process->timeout === 60);
El $process
pasado a la función anónima
assertRan
es una instancia de Illuminate\Process\PendingProcess
, mientras que el $result
es una instancia de Illuminate\Contracts\Process\ProcessResult
.
assertDidntRun
Afirmar que un proceso dado no fue invocado:
use Illuminate\Support\Facades\Process; Process::assertDidntRun('ls -la');
Al igual que el método assertRan
, el método assertDidntRun
también acepta una función anónima, que recibirá una instancia de un proceso y un resultado del proceso, lo que te permitirá inspeccionar las opciones configuradas del proceso. Si esta función anónima devuelve true
, la afirmación "fallará":
Process::assertDidntRun(fn (PendingProcess $process, ProcessResult $result) => $process->command === 'ls -la');
assertRanTimes
Asegúrate de que un proceso dado fue invocado un número dado de veces:
use Illuminate\Support\Facades\Process; Process::assertRanTimes('ls -la', times: 3);
El método assertRanTimes
también acepta una función anónima, que recibirá una instancia de un proceso y un resultado del proceso, lo que te permitirá inspeccionar las opciones configuradas del proceso. Si esta función anónima devuelve true
y el proceso fue invocado el número especificado de veces, la aserción "pasará":
Process::assertRanTimes(function (PendingProcess $process, ProcessResult $result) { return $process->command === 'ls -la';}, times: 3);
Previniendo Procesos Errantes
Si deseas asegurarte de que todos los procesos invocados hayan sido simulados a lo largo de tu prueba individual o de tu suite de pruebas completa, puedes llamar al método preventStrayProcesses
. Después de llamar a este método, cualquier proceso que no tenga un resultado simulado correspondiente lanzará una excepción en lugar de iniciar un proceso real:
use Illuminate\Support\Facades\Process; Process::preventStrayProcesses(); Process::fake([ 'ls *' => 'Test output...',]); // Fake response is returned...Process::run('ls -la'); // An exception is thrown...Process::run('bash import.sh');