#Introducción
Laravel proporciona una API expresiva y minimalista alrededor del componente Process de Symfony, permitiéndole invocar procesos externos desde su aplicación Laravel de manera conveniente. Las características de procesos de Laravel están enfocadas en los casos de uso más comunes y en ofrecer una experiencia de desarrollo excelente.
#Invocando Procesos
Para invocar un proceso, puede usar los métodos run y start que ofrece el facade Process. El método run invocará un proceso y esperará a que termine su ejecución, mientras que el método start se usa para la ejecución asíncrona de procesos. Examinaremos ambos enfoques en esta documentación. Primero, veamos cómo invocar un proceso básico y síncrono 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 que devuelve el método run ofrece una variedad de métodos útiles que pueden usarse para inspeccionar el resultado del proceso:
$result = Process::run('ls -la');
$result->successful();
$result->failed();
$result->exitCode();
$result->output();
$result->errorOutput();
#Lanzando Excepciones
Si tiene un resultado de proceso y desea lanzar una instancia de Illuminate\Process\Exceptions\ProcessFailedException si el código de salida es mayor que cero (indicando fallo), puede 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, puede necesitar personalizar el comportamiento de un proceso antes de invocarlo. Afortunadamente, Laravel le permite ajustar varias características del proceso, como el directorio de trabajo, el tiempo de espera y las variables de entorno.
#Ruta del Directorio de Trabajo
Puede usar el método path para especificar el directorio de trabajo del proceso. Si no se invoca este método, el proceso heredará el directorio de trabajo del script PHP que se está ejecutando actualmente:
$result = Process::path(__DIR__)->run('ls -la');
#Entrada
Puede proporcionar entrada a través de la "entrada estándar" del proceso usando el método input:
$result = Process::input('Hello World')->run('cat');
#Tiempos de Espera
Por defecto, los procesos lanzarán una instancia de Illuminate\Process\Exceptions\ProcessTimedOutException después de ejecutarse por más de 60 segundos. Sin embargo, puede personalizar este comportamiento mediante el método timeout:
$result = Process::timeout(120)->run('bash import.sh');
O, si desea deshabilitar completamente el tiempo de espera del proceso, puede invocar el método forever:
$result = Process::forever()->run('bash import.sh');
El método idleTimeout puede usarse 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 proporcionarse al proceso mediante el 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 desea eliminar una variable de entorno heredada del proceso invocado, puede proporcionar esa variable con un valor de false:
$result = Process::forever()
->env(['LOAD_PATH' => false])
->run('bash import.sh');
#Modo TTY
El método tty puede usarse para habilitar el modo TTY para su proceso. El modo TTY conecta la entrada y salida del proceso con la entrada y salida de su programa, permitiendo que su 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 accederse usando 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 recopilarse en tiempo real pasando un closure como segundo argumento al método run. El closure recibirá dos argumentos: el "tipo" de salida (stdout o stderr) y la cadena de salida misma:
$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')) {
// ...
}
#Deshabilitando la Salida del Proceso
Si su proceso está generando una cantidad significativa de salida que no le interesa, puede conservar memoria deshabilitando completamente la recuperación de salida. Para lograr esto, invoque el método quietly mientras construye el proceso:
use Illuminate\Support\Facades\Process;
$result = Process::quietly()->run('bash import.sh');
#Pipelines
A veces puede querer hacer que la salida de un proceso sea la entrada de otro proceso. Esto se conoce comúnmente como "encadenar" la salida de un proceso en otro. El método pipe proporcionado por el facade Process facilita esta tarea. El método pipe ejecutará los procesos encadenados de forma síncrona y devolverá el resultado del último proceso en la cadena:
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 necesita personalizar los procesos individuales que componen el pipeline, puede simplemente pasar un arreglo de cadenas de comandos al método pipe:
$result = Process::pipe([
'cat example.txt',
'grep -i "laravel"',
]);
La salida del proceso puede recopilarse en tiempo real pasando un closure como segundo argumento al método pipe. El closure recibirá dos argumentos: el "tipo" de salida (stdout o stderr) y la cadena de salida misma:
$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 le permite asignar claves de cadena a cada proceso dentro de un pipeline mediante el método as. Esta clave también será pasada al closure de salida proporcionado al método pipe, permitiéndole 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 síncrona, el método start puede usarse para invocar un proceso de forma asíncrona. Esto permite que su aplicación continúe realizando otras tareas mientras el proceso se ejecuta en segundo plano. Una vez que el proceso ha sido invocado, 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á notado, puede invocar el método wait para esperar hasta que el proceso termine su ejecución y recuperar la instancia del resultado del proceso:
$process = Process::timeout(120)->start('bash import.sh');
// ...
$result = $process->wait();
#IDs y Señales de Procesos
El método id puede usarse para recuperar el ID de proceso asignado por el sistema operativo del proceso en ejecución:
$process = Process::start('bash import.sh');
return $process->id();
Puede usar el método signal para enviar una "señal" al proceso en ejecución. Una lista de constantes de señales predefinidas puede encontrarse en la documentación de PHP:
$process->signal(SIGUSR2);
#Salida de Procesos Asíncronos
Mientras un proceso asíncrono está en ejecución, puede acceder a toda su salida actual usando los métodos output y errorOutput; sin embargo, puede 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 de procesos asíncronos pasando un closure como segundo argumento al método start. El closure recibirá dos argumentos: el "tipo" de salida (stdout o stderr) y la cadena de salida misma:
$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 pool de procesos asíncronos concurrentes, permitiéndole ejecutar muchas tareas simultáneamente con facilidad. Para comenzar, invoque el método pool, que acepta un closure que recibe una instancia de Illuminate\Process\Pool.
Dentro de este closure, puede definir los procesos que pertenecen al pool. Una vez que un pool de procesos es iniciado mediante el método start, puede acceder a la colección de procesos en ejecución mediante el 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 puede ver, puede esperar a que todos los procesos del pool terminen su ejecución y resolver sus resultados mediante el método wait. El método wait devuelve un objeto accesible como arreglo que le permite acceder a la instancia del resultado de proceso de cada proceso en el pool por su clave:
$results = $pool->wait();
echo $results[0]->output();
O, para mayor comodidad, el método concurrently puede usarse 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 arreglos 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();
#Nombrando Procesos en el Pool
Acceder a los resultados del pool de procesos mediante una clave numérica no es muy expresivo; por lo tanto, Laravel le permite asignar claves de cadena a cada proceso dentro de un pool mediante el método as. Esta clave también será pasada al closure proporcionado al método start, permitiéndole 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();
#IDs y Señales de Procesos en el Pool
Dado que el método running del pool de procesos proporciona una colección de todos los procesos invocados dentro del pool, puede acceder fácilmente a los IDs subyacentes de los procesos del pool:
$processIds = $pool->running()->each->id();
Y, para mayor comodidad, puede invocar el método signal en un pool de procesos para enviar una señal a todos los procesos dentro del pool:
$pool->signal(SIGUSR2);
#Pruebas
Muchos servicios de Laravel proporcionan funcionalidades para ayudarle a escribir pruebas de manera fácil y expresiva, y el servicio de procesos de Laravel no es la excepción. El método fake del facade Process le permite indicar a Laravel que devuelva resultados simulados / falsos cuando se invocan procesos.
#Falsificando Procesos
Para explorar la capacidad de Laravel para falsificar 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 indicar a Laravel que devuelva un resultado de proceso falso y exitoso para cada proceso invocado llamando al método fake en el facade Process sin argumentos. Además, incluso podemos asegurar que un proceso dado fue "ejecutado":
<?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');
// Aserción simple de proceso...
Process::assertRan('bash import.sh');
// O, inspeccionando la configuración del proceso...
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 el facade Process indicará a Laravel que siempre devuelva un resultado de proceso exitoso sin salida. Sin embargo, puede especificar fácilmente la salida y el código de salida para procesos falsificados usando el método result del facade Process:
Process::fake([
'*' => Process::result(
output: 'Test output',
errorOutput: 'Test error output',
exitCode: 1,
),
]);
#Falsificando Procesos Específicos
Como habrá notado en un ejemplo anterior, el facade Process le permite especificar diferentes resultados falsos por proceso pasando un arreglo al método fake.
Las claves del arreglo deben representar patrones de comandos que desea falsificar y sus resultados asociados. El carácter * puede usarse como comodín. Cualquier comando de proceso que no haya sido falsificado será realmente invocado. Puede usar el método result del facade 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 necesita personalizar el código de salida o la salida de error de un proceso falsificado, puede encontrar más conveniente especificar los resultados falsos del proceso como cadenas simples:
Process::fake([
'cat *' => 'Test "cat" output',
'ls *' => 'Test "ls" output',
]);
#Falsificando Secuencias de Procesos
Si el código que está probando invoca múltiples procesos con el mismo comando, puede querer asignar un resultado falso diferente a cada invocación de proceso. Puede lograr esto mediante el método sequence del facade Process:
Process::fake([
'ls *' => Process::sequence()
->push(Process::result('First invocation'))
->push(Process::result('Second invocation')),
]);
#Falsificando Ciclos de Vida de Procesos Asíncronos
Hasta ahora, hemos discutido principalmente la falsificación de procesos que se invocan de forma síncrona usando el método run. Sin embargo, si está intentando probar código que interactúa con procesos asíncronos invocados mediante start, puede necesitar un enfoque más sofisticado para describir sus procesos falsos.
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 falsificar correctamente este proceso, necesitamos poder describir cuántas veces el método running debe devolver true. Además, podemos querer especificar múltiples líneas de salida que deben devolverse en secuencia. Para lograr esto, podemos usar el método describe del facade 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),
]);
Analicemos 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 falso. Finalmente, el método iterations puede usarse para especificar cuántas veces el método running debe devolver true.
#Aserciones Disponibles
Como se discutió anteriormente, Laravel proporciona varias aserciones de procesos para sus pruebas de características. Discutiremos cada una de estas aserciones a continuación.
#assertRan
Asegura que un proceso dado fue invocado:
use Illuminate\Support\Facades\Process;
Process::assertRan('ls -la');
El método assertRan también acepta un closure, que recibirá una instancia de un proceso y un resultado de proceso, permitiéndole inspeccionar las opciones configuradas del proceso. Si este closure devuelve true, la aserción "pasará":
Process::assertRan(fn ($process, $result) =>
$process->command === 'ls -la' &&
$process->path === __DIR__ &&
$process->timeout === 60
);
El $process pasado al closure de assertRan es una instancia de Illuminate\Process\PendingProcess, mientras que $result es una instancia de Illuminate\Contracts\Process\ProcessResult.
#assertDidntRun
Asegura 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 un closure, que recibirá una instancia de un proceso y un resultado de proceso, permitiéndole inspeccionar las opciones configuradas del proceso. Si este closure devuelve true, la aserción "fallará":
Process::assertDidntRun(fn (PendingProcess $process, ProcessResult $result) =>
$process->command === 'ls -la'
);
#assertRanTimes
Asegura que un proceso dado fue invocado un número específico de veces:
use Illuminate\Support\Facades\Process;
Process::assertRanTimes('ls -la', times: 3);
El método assertRanTimes también acepta un closure, que recibirá una instancia de un proceso y un resultado de proceso, permitiéndole inspeccionar las opciones configuradas del proceso. Si este closure 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 desea asegurarse de que todos los procesos invocados hayan sido falsificados durante su prueba individual o suite completa de pruebas, puede llamar al método preventStrayProcesses. Después de llamar a este método, cualquier proceso que no tenga un resultado falso correspondiente lanzará una excepción en lugar de iniciar un proceso real:
use Illuminate\Support\Facades\Process;
Process::preventStrayProcesses();
Process::fake([
'ls *' => 'Test output...',
]);
// Se devuelve la respuesta falsa...
Process::run('ls -la');
// Se lanza una excepción...
Process::run('bash import.sh');