Prologo
Primi Passi
Architettura
Le Basi
- Routing
- Middleware
- Protezione da CSRF
- Controller
- Richieste
- Risposte
- Views
- Blade
- Vite
- URL
- Sessioni
- Validazione
- Errori
- Logging
Approfondimenti
- Artisan
- Broadcasting
- Cache
- Collezioni
- Concorrenza
- Contesto
- Contratti
- Eventi
- File System
- Helpers
- Client HTTP
- Localizzazione
- Notifiche
- Sviluppo di Package
- Processi
- Code
- Rate-limiting
- Stringhe
- Scheduling di Task
Sicurezza
Database
Eloquent ORM
Testing
Package
Processi
Introduzione
Laravel offre un’API espressiva e minimal intorno al componente Symfony Process, permettendoti di invocare facilmente processi esterni dalla tua applicazione Laravel. Le funzionalità di processo di Laravel si concentrano sui casi d’uso più comuni e offrono un’ottima esperienza per gli sviluppatori.
Invocare i Processi
Per avviare un processo, puoi usare i metodi run
e start
offerti dalla facade Process
. Il metodo run
esegue un processo e attende che termini, mentre il metodo start
serve per l’esecuzione asincrona. In questa documentazione esamineremo entrambe le modalità. Innanzitutto, vediamo come avviare un processo semplice in modo sincrono e verificarne il risultato:
use Illuminate\Support\Facades\Process;
$result = Process::run('ls -la');
return $result->output();
Naturalmente, l’istanza Illuminate\Contracts\Process\ProcessResult
restituita dal metodo run
offre diversi metodi utili per esaminare il risultato del processo:
$result = Process::run('ls -la');
$result->successful();
$result->failed();
$result->exitCode();
$result->output();
$result->errorOutput();
Lancio delle Eccezioni
Se il processo ha un risultato e desideri lanciare un’istanza di Illuminate\Process\Exceptions\ProcessFailedException
nel caso in cui il codice di uscita è maggiore di zero (indicando quindi un fallimento), puoi utilizzare i metodi throw
e throwIf
. Se il processo non fallisce, verrà restituita l’istanza del risultato del processo:
$result = Process::run('ls -la')->throw();
$result = Process::run('ls -la')->throwIf($condition);
Opzioni del Processo
Naturalmente, potresti dover personalizzare il comportamento di un processo prima di eseguirlo. Fortunatamente, Laravel permette di modificare varie caratteristiche del processo, come la directory di lavoro, il timeout e le variabili d’ambiente.
Percorso della Directory di Lavoro
Puoi usare il metodo path
per specificare la directory di lavoro del processo. Se questo metodo non viene invocato, il processo erediterà la directory di lavoro dello script PHP attualmente in esecuzione:
$result = Process::path(__DIR__)->run('ls -la');
Input
Puoi fornire input attraverso lo "standard input" del processo utilizzando il metodo input
:
$result = Process::input('Hello World')->run('cat');
Timeout
Per default, i processi lanceranno un’istanza di Illuminate\Process\Exceptions\ProcessTimedOutException
se eseguono per più di 60 secondi. Tuttavia, puoi personalizzare questo comportamento usando il metodo timeout
:
$result = Process::timeout(120)->run('bash import.sh');
Oppure, se vuoi disabilitare completamente il timeout del processo, puoi usare il metodo forever
:
$result = Process::forever()->run('bash import.sh');
Il metodo idleTimeout
serve per specificare il numero massimo di secondi che il processo può eseguire senza restituire alcun output:
$result = Process::timeout(60)->idleTimeout(30)->run('bash import.sh');
Variabili di Ambiente
Le variabili di ambiente possono essere fornite al processo tramite il metodo env
. Il processo invocato erediterà inoltre tutte le variabili di ambiente definite dal tuo sistema:
$result = Process::forever()
->env(['IMPORT_PATH' => __DIR__])
->run('bash import.sh');
Se desideri rimuovere una variabile di ambiente ereditata dal processo invocato, puoi fornire quella variabile di ambiente con un valore di false
:
$result = Process::forever()
->env(['LOAD_PATH' => false])
->run('bash import.sh');
Modalità TTY
Il metodo tty
può essere utilizzato per abilitare la modalità TTY per il tuo processo. La modalità TTY collega l’input e l’output del processo all’input e all’output del tuo programma, permettendo al tuo processo di aprire un editor come Vim o Nano come processo:
Process::forever()->tty()->run('vim');
Output del Processo
Come discusso in precedenza, l’output del processo può essere accesso usando i metodi output
(stdout) e errorOutput
(stderr) sul risultato di un processo:
use Illuminate\Support\Facades\Process;
$result = Process::run('ls -la');
echo $result->output();
echo $result->errorOutput();
Tuttavia, l’output può anche essere raccolto in tempo reale passando una closure come secondo argomento al metodo run
. La closure riceverà due argomenti: il "tipo" di output (stdout
o stderr
) e la stringa di output stessa:
$result = Process::run('ls -la', function (string $type, string $output) {
echo $output;
});
Laravel offre anche i metodi seeInOutput
e seeInErrorOutput
, che forniscono un modo comodo per determinare se una determinata stringa è presente nell’output del processo:
if (Process::run('ls -la')->seeInOutput('laravel')) {
// ...
}
Disabilitare l’Output del Processo
Se il tuo processo genera una quantità significativa di output che non ti interessa, puoi risparmiare memoria disabilitando completamente la cattura dell’output. Per fare ciò, invoca il metodo quietly
durante la creazione del processo:
use Illuminate\Support\Facades\Process;
$result = Process::quietly()->run('bash import.sh');
Pipeline
A volte potresti voler utilizzare l’output di un processo come input per un altro. Questo è spesso chiamato "piping". Il metodo pipe
delle facciate Process
semplifica questa operazione. Il metodo pipe
esegue i processi collegati in modo sincrono e restituisce il risultato dell’ultimo processo nella pipeline:
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()) {
// ...
}
Se non hai bisogno di personalizzare i singoli processi della pipeline, puoi semplicemente passare un array di stringhe di comando al metodo pipe
:
$result = Process::pipe([
'cat example.txt',
'grep -i "laravel"',
]);
L’output del processo può essere raccolto in tempo reale passando una closure come secondo argomento al metodo pipe
. La closure riceverà due argomenti: il "tipo" di output (stdout
o stderr
) e la stringa di output stessa:
$result = Process::pipe(function (Pipe $pipe) {
$pipe->command('cat example.txt');
$pipe->command('grep -i "laravel"');
}, function (string $type, string $output) {
echo $output;
});
Laravel ti permette anche di assegnare chiavi stringa a ciascun processo all’interno di una pipeline tramite il metodo as
. Questa chiave sarà passata alla closure di output fornita al metodo pipe
, permettendoti di determinare a quale processo appartiene l’output:
$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) {
// ...
});
Processi Asincroni
Mentre il metodo run
invoca i processi in modo sincrono, il metodo start
può essere utilizzato per avviare un processo in modo asincrono. Questo permette alla tua applicazione di continuare a eseguire altre attività mentre il processo opera in background. Una volta avviato il processo, puoi utilizzare il metodo running
per verificare se il processo è ancora in esecuzione:
$process = Process::timeout(120)->start('bash import.sh');
while ($process->running()) {
// ...
}
$result = $process->wait();
Come avrai notato, puoi invocare il metodo wait
per attendere che il processo finisca l’esecuzione e recuperare l’istanza del risultato del processo:
$process = Process::timeout(120)->start('bash import.sh');
// ...
$result = $process->wait();
ID di processo e Segnali
Il metodo id
può essere utilizzato per ottenere l’ID di processo assegnato dal sistema operativo al processo in esecuzione:
$process = Process::start('bash import.sh');
return $process->id();
Puoi usare il metodo signal
per inviare un "segnale" al processo in esecuzione. Un elenco delle costanti di segnale predefinite si trova nella documentazione PHP:
$process->signal(SIGUSR2);
Output del Processo Asincrono
Durante l’esecuzione di un processo asincrono, puoi accedere all’intero output corrente utilizzando i metodi output
e errorOutput
; tuttavia, puoi usare latestOutput
e latestErrorOutput
per ottenere l’output generato dal processo da quando è stato recuperato l’ultimo output:
$process = Process::timeout(120)->start('bash import.sh');
while ($process->running()) {
echo $process->latestOutput();
echo $process->latestErrorOutput();
sleep(1);
}
Analogamente al metodo run
, puoi raccogliere l’output in tempo reale dai processi asincroni passando una closure come secondo argomento al metodo start
. La closure riceverà due argomenti: il "tipo" di output (stdout
o stderr
) e la stringa dell’output stesso:
$process = Process::start('bash import.sh', function (string $type, string $output) {
echo $output;
});
$result = $process->wait();
Invece di aspettare che il processo finisca, puoi usare il metodo waitUntil
per interrompere l’attesa basandoti sull’output del processo. Laravel smetterà di attendere la fine del processo quando la closure passata al metodo waitUntil
restituisce true
:
$process = Process::start('bash import.sh');
$process->waitUntil(function (string $type, string $output) {
return $output === 'Ready...';
});
Processi Concorrenti
Laravel rende anche facile gestire un pool di processi concorrenti e asincroni, permettendoti di eseguire facilmente molte attività contemporaneamente. Per iniziare, chiama il metodo pool
, che accetta una closure che riceve un’istanza di Illuminate\Process\Pool
.
All’interno di questa closure, puoi definire i processi che appartengono al pool. Una volta avviato un pool di processi tramite il metodo start
, puoi accedere alla collezione dei processi in esecuzione tramite il metodo 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();
Come puoi vedere, puoi attendere che tutti i processi del pool finiscano di eseguire e risolvere i loro risultati tramite il metodo wait
. Il metodo wait
ritorna un oggetto accessibile come array che ti permette di accedere all’istanza del risultato di ogni processo nel pool tramite la sua chiave:
$results = $pool->wait();
echo $results[0]->output();
Oppure, per comodità, il metodo concurrently
può essere usato per avviare un pool di processi asincroni e attendere immediatamente i suoi risultati. Questo può fornire una sintassi particolarmente espressiva quando combinato con le capacità di destrutturazione degli array di 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();
Assegnazione di nomi ai processi del pool
Accedere ai risultati del pool di processi tramite una chiave numerica non è molto chiaro; per questo Laravel permette di assegnare chiavi stringa a ogni processo nel pool usando il metodo as
. Questa chiave verrà anche passata alla closure del metodo start
, permettendoti di sapere a quale processo appartiene l’output:
$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();
ID dei Processi nel Pool e Segnali
Poiché il metodo running
del pool di processi fornisce una collezione di tutti i processi invocati all’interno del pool, puoi facilmente accedere agli ID dei processi sottostanti:
$processIds = $pool->running()->each->id();
E, per comodità, puoi invocare il metodo signal
su un pool di processi per inviare un segnale a ogni processo all’interno del pool:
$pool->signal(SIGUSR2);
Testing
Molti servizi di Laravel offrono funzionalità per aiutarti a scrivere test in modo semplice ed espressivo, e anche il servizio di processo di Laravel non fa eccezione. Il metodo fake
della facade Process
ti permette di istruire Laravel a restituire risultati simulati / fittizi quando vengono invocati i processi.
Simulare i Processi
Per esplorare la capacità di Laravel di simulare processi, immaginiamo una route che invoca un processo:
use Illuminate\Support\Facades\Process;
use Illuminate\Support\Facades\Route;
Route::get('/import', function () {
Process::run('bash import.sh');
return 'Import complete!';
});
Durante il test di questa route, possiamo istruire Laravel a restituire un risultato di processo simulato e di successo per ogni processo invocato chiamando il metodo fake
sulla facade Process
senza argomenti. Inoltre, possiamo anche verificare che un determinato processo sia stato "eseguito":
<?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;
});
}
}
Come discusso, invocare il metodo fake
sulla facade Process
istruirà Laravel a restituire sempre un risultato di processo di successo senza output. Tuttavia, puoi facilmente specificare l’output e il codice di uscita per i processi simulati utilizzando il metodo result
della facade Process
:
Process::fake([
'*' => Process::result(
output: 'Test output',
errorOutput: 'Test error output',
exitCode: 1,
),
]);
Simulare Processi Specifici
Come avrai notato in un esempio precedente, la facade Process
ti permette di specificare risultati fittizi diversi per ogni processo passando un array al metodo fake
.
Le chiavi dell’array devono rappresentare i pattern dei comandi che desideri simulare e i loro risultati associati. Il carattere *
può essere utilizzato come carattere jolly. Qualsiasi comando di processo che non è stato simulato verrà effettivamente eseguito. Puoi usare il metodo result
della facade Process
per creare risultati fittizi per questi comandi:
Process::fake([
'cat *' => Process::result(
output: 'Test "cat" output',
),
'ls *' => Process::result(
output: 'Test "ls" output',
),
]);
Se non hai bisogno di personalizzare il codice di uscita o l’output di errore di un processo simulato, potresti trovare più comodo specificare i risultati del processo fittizio come semplici stringhe:
Process::fake([
'cat *' => 'Test "cat" output',
'ls *' => 'Test "ls" output',
]);
Sequenze di Processi Simulati
Se il codice che stai testando invoca più processi con lo stesso comando, potresti voler assegnare un risultato di processo simulato diverso a ciascuna invocazione del processo. Puoi farlo tramite il metodo sequence
della facade Process
:
Process::fake([
'ls *' => Process::sequence()
->push(Process::result('Prima invocazione'))
->push(Process::result('Seconda invocazione')),
]);
Simulare i Cicli di Vita di Processi Asincroni
Finora, abbiamo principalmente parlato di simulare processi invocati in modo sincrono utilizzando il metodo run
. Tuttavia, se stai cercando di testare del codice che interagisce con processi asincroni invocati tramite start
, potresti aver bisogno di un approccio più sofisticato per descrivere i tuoi processi simulati.
Ad esempio, immaginiamo la seguente route che interagisce con un processo asincrono:
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';
});
Per simulare correttamente questo processo, dobbiamo poter descrivere quante volte il metodo running
dovrebbe restituire true
. Inoltre, potremmo voler specificare più linee di output che dovrebbero essere restituite in sequenza. Per ottenere ciò, possiamo utilizzare il metodo describe
della 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),
]);
Analizziamo l’esempio sopra. Utilizzando i metodi output
ed errorOutput
, possiamo specificare più linee di output che verranno restituite in sequenza. Il metodo exitCode
può essere usato per specificare il codice di uscita finale del processo simulato. Infine, il metodo iterations
può essere utilizzato per specificare quante volte il metodo running
dovrebbe restituire true
.
Asserzioni Disponibili
Come visto in precedenza, Laravel fornisce diverse process assertions per i tuoi feature test. Di seguito discuteremo ciascuna di queste asserzioni.
assertRan
Verifica che un determinato processo sia stato eseguito:
use Illuminate\Support\Facades\Process;
Process::assertRan('ls -la');
Il metodo assertRan
accetta anche una closure, che riceverà un’istanza di un processo e un risultato del processo, permettendoti di esaminare le opzioni configurate del processo. Se questa closure restituisce true
, l’asserzione avrà successo:
Process::assertRan(fn ($process, $result) =>
$process->command === 'ls -la' &&
$process->path === __DIR__ &&
$process->timeout === 60
);
L’$process
passato alla closure assertRan
è un’istanza di Illuminate\Process\PendingProcess
, mentre il $result
è un’istanza di Illuminate\Contracts\Process\ProcessResult
.
assertDidntRun
Verifica che un determinato processo non sia stato eseguito:
use Illuminate\Support\Facades\Process;
Process::assertDidntRun('ls -la');
Come il metodo assertRan
, anche il metodo assertDidntRun
accetta una closure, che riceve un’istanza di un processo e un risultato del processo, permettendoti di controllare le opzioni configurate del processo. Se questa closure restituisce true
, l’asserzione "fallirà":
Process::assertDidntRun(fn (PendingProcess $process, ProcessResult $result) =>
$process->command === 'ls -la'
);
assertRanTimes
Verifica che un determinato processo sia stato eseguito un numero specifico di volte:
use Illuminate\Support\Facades\Process;
Process::assertRanTimes('ls -la', times: 3);
Il metodo assertRanTimes
accetta anche una closure, che riceverà un’istanza di un processo e il risultato del processo, permettendoti di controllare le opzioni configurate del processo. Se questa closure restituisce true
e il processo è stato eseguito il numero di volte specificato, l’asserzione "passerà":
Process::assertRanTimes(function (PendingProcess $process, ProcessResult $result) {
return $process->command === 'ls -la';
}, times: 3);
Prevenire Processi Indesiderati
Se desideri assicurarti che tutti i processi invocati siano stati simulati durante un test individuale o l’intera suite di test, puoi chiamare il metodo preventStrayProcesses
. Dopo aver chiamato questo metodo, qualsiasi processo che non abbia un risultato fittizio corrispondente lancerà un’eccezione invece di avviare un processo reale:
use Illuminate\Support\Facades\Process;
Process::preventStrayProcesses();
Process::fake([
'ls *' => 'Test output...',
]);
// Viene restituita una risposta fittizia...
Process::run('ls -la');
// Viene lanciata un'eccezione...
Process::run('bash import.sh');