程序
簡介
Laravel 在 Symfony Process component 周圍提供了一個富有表現力且極簡的 API,讓您可以從您的 Laravel 應用程式中方便地調用外部程序。 Laravel 的程序功能專注於最常見的用例和絕佳的開發者體驗。
調用程序
若要調用程序,您可以使用 Process
facade 提供的 run
和 start
方法。 run
方法將調用程序並等待程序完成執行,而 start
方法用於異步程序執行。我們將在本文件中檢視這兩種方法。首先,讓我們檢視如何調用基本的同步程序並檢查其結果
1use Illuminate\Support\Facades\Process;2 3$result = Process::run('ls -la');4 5return $result->output();
當然,run
方法傳回的 Illuminate\Contracts\Process\ProcessResult
實例提供了各種有用的方法,可用於檢查程序結果
1$result = Process::run('ls -la');2 3$result->successful();4$result->failed();5$result->exitCode();6$result->output();7$result->errorOutput();
拋出例外
如果您有程序結果,並且希望在退出代碼大於零時(表示失敗)拋出 Illuminate\Process\Exceptions\ProcessFailedException
的實例,您可以使用 throw
和 throwIf
方法。如果程序未失敗,則將傳回程序結果實例
1$result = Process::run('ls -la')->throw();2 3$result = Process::run('ls -la')->throwIf($condition);
程序選項
當然,您可能需要在調用程序之前自訂程序的行為。 幸運的是,Laravel 允許您調整各種程序功能,例如工作目錄、逾時和環境變數。
工作目錄路徑
您可以使用 path
方法來指定程序的工作目錄。 如果未調用此方法,則程序將繼承目前正在執行的 PHP 腳本的工作目錄
1$result = Process::path(__DIR__)->run('ls -la');
輸入
您可以使用 input
方法,透過程序的「標準輸入」來提供輸入
1$result = Process::input('Hello World')->run('cat');
逾時
預設情況下,程序在執行超過 60 秒後將拋出 Illuminate\Process\Exceptions\ProcessTimedOutException
的實例。 但是,您可以透過 timeout
方法自訂此行為
1$result = Process::timeout(120)->run('bash import.sh');
或者,如果您想完全停用程序逾時,您可以調用 forever
方法
1$result = Process::forever()->run('bash import.sh');
idleTimeout
方法可用於指定程序在沒有傳回任何輸出情況下可以執行的最大秒數
1$result = Process::timeout(60)->idleTimeout(30)->run('bash import.sh');
環境變數
可以透過 env
方法將環境變數提供給程序。 調用的程序也將繼承系統定義的所有環境變數
1$result = Process::forever()2 ->env(['IMPORT_PATH' => __DIR__])3 ->run('bash import.sh');
如果您希望從調用的程序中移除繼承的環境變數,您可以提供值為 false
的環境變數
1$result = Process::forever()2 ->env(['LOAD_PATH' => false])3 ->run('bash import.sh');
TTY 模式
tty
方法可用於為您的程序啟用 TTY 模式。 TTY 模式將程序的輸入和輸出連接到程式的輸入和輸出,允許您的程序將 Vim 或 Nano 等編輯器作為程序開啟
1Process::forever()->tty()->run('vim');
程序輸出
如先前討論,可以使用程序結果上的 output
(stdout) 和 errorOutput
(stderr) 方法來存取程序輸出
1use Illuminate\Support\Facades\Process;2 3$result = Process::run('ls -la');4 5echo $result->output();6echo $result->errorOutput();
但是,也可以透過傳遞閉包作為 run
方法的第二個引數來即時收集輸出。 閉包將接收兩個引數:「輸出類型」(stdout
或 stderr
)和輸出字串本身
1$result = Process::run('ls -la', function (string $type, string $output) {2 echo $output;3});
Laravel 也提供了 seeInOutput
和 seeInErrorOutput
方法,這些方法提供了一種方便的方式來判斷給定的字串是否包含在程序的輸出中
1if (Process::run('ls -la')->seeInOutput('laravel')) {2 // ...3}
停用程序輸出
如果您的程序正在寫入大量您不感興趣的輸出,您可以透過完全停用輸出檢索來節省記憶體。 若要完成此操作,請在建構程序時調用 quietly
方法
1use Illuminate\Support\Facades\Process;2 3$result = Process::quietly()->run('bash import.sh');
管線
有時您可能希望將一個程序的輸出作為另一個程序的輸入。 這通常稱為將程序的輸出「管道」到另一個程序中。 Process
facade 提供的 pipe
方法使這項操作變得容易。 pipe
方法將同步執行管道程序,並傳回管道中最後一個程序的程序結果
1use Illuminate\Process\Pipe; 2use Illuminate\Support\Facades\Process; 3 4$result = Process::pipe(function (Pipe $pipe) { 5 $pipe->command('cat example.txt'); 6 $pipe->command('grep -i "laravel"'); 7}); 8 9if ($result->successful()) {10 // ...11}
如果您不需要自訂組成管道的個別程序,您可以簡單地將命令字串陣列傳遞給 pipe
方法
1$result = Process::pipe([2 'cat example.txt',3 'grep -i "laravel"',4]);
可以透過傳遞閉包作為 pipe
方法的第二個引數來即時收集程序輸出。 閉包將接收兩個引數:「輸出類型」(stdout
或 stderr
)和輸出字串本身
1$result = Process::pipe(function (Pipe $pipe) {2 $pipe->command('cat example.txt');3 $pipe->command('grep -i "laravel"');4}, function (string $type, string $output) {5 echo $output;6});
Laravel 也允許您透過 as
方法為管道中的每個程序指派字串鍵。 此鍵也將傳遞給提供給 pipe
方法的輸出閉包,讓您可以判斷輸出屬於哪個程序
1$result = Process::pipe(function (Pipe $pipe) {2 $pipe->as('first')->command('cat example.txt');3 $pipe->as('second')->command('grep -i "laravel"');4})->start(function (string $type, string $output, string $key) {5 // ...6});
異步程序
雖然 run
方法同步調用程序,但 start
方法可用於異步調用程序。 這允許您的應用程式在程序於背景執行時繼續執行其他任務。 程序調用後,您可以使用 running
方法來判斷程序是否仍在執行
1$process = Process::timeout(120)->start('bash import.sh');2 3while ($process->running()) {4 // ...5}6 7$result = $process->wait();
您可能已經注意到,您可以調用 wait
方法來等待程序完成執行並檢索程序結果實例
1$process = Process::timeout(120)->start('bash import.sh');2 3// ...4 5$result = $process->wait();
程序 ID 和訊號
id
方法可用於檢索作業系統指派給執行中程序的程序 ID
1$process = Process::start('bash import.sh');2 3return $process->id();
您可以使用 signal
方法將「訊號」傳送到執行中的程序。 預定義訊號常數的清單可以在 PHP 文件中找到
1$process->signal(SIGUSR2);
異步程序輸出
當異步程序正在執行時,您可以使用 output
和 errorOutput
方法存取其整個目前輸出;但是,您可以使用 latestOutput
和 latestErrorOutput
來存取自上次檢索輸出以來程序產生的輸出
1$process = Process::timeout(120)->start('bash import.sh');2 3while ($process->running()) {4 echo $process->latestOutput();5 echo $process->latestErrorOutput();6 7 sleep(1);8}
與 run
方法類似,也可以透過傳遞閉包作為 start
方法的第二個引數,從異步程序即時收集輸出。 閉包將接收兩個引數:「輸出類型」(stdout
或 stderr
)和輸出字串本身
1$process = Process::start('bash import.sh', function (string $type, string $output) {2 echo $output;3});4 5$result = $process->wait();
您可以使用 waitUntil
方法根據程序的輸出停止等待,而不是等待程序完成。 當提供給 waitUntil
方法的閉包傳回 true
時,Laravel 將停止等待程序完成
1$process = Process::start('bash import.sh');2 3$process->waitUntil(function (string $type, string $output) {4 return $output === 'Ready...';5});
並行程序
Laravel 也讓管理並行異步程序的池變得輕而易舉,讓您可以輕鬆地同時執行多個任務。 若要開始使用,請調用 pool
方法,該方法接受接收 Illuminate\Process\Pool
實例的閉包。
在此閉包中,您可以定義屬於池的程序。 一旦透過 start
方法啟動程序池,您就可以透過 running
方法存取執行中程序的 集合
1use Illuminate\Process\Pool; 2use Illuminate\Support\Facades\Process; 3 4$pool = Process::pool(function (Pool $pool) { 5 $pool->path(__DIR__)->command('bash import-1.sh'); 6 $pool->path(__DIR__)->command('bash import-2.sh'); 7 $pool->path(__DIR__)->command('bash import-3.sh'); 8})->start(function (string $type, string $output, int $key) { 9 // ...10});11 12while ($pool->running()->isNotEmpty()) {13 // ...14}15 16$results = $pool->wait();
如您所見,您可以等待所有池程序完成執行,並透過 wait
方法解析其結果。 wait
方法傳回可存取陣列的物件,讓您可以按鍵存取池中每個程序的程序結果實例
1$results = $pool->wait();2 3echo $results[0]->output();
或者,為了方便起見,可以使用 concurrently
方法來啟動異步程序池,並立即等待其結果。 當與 PHP 的陣列解構功能結合使用時,這可以提供特別富有表現力的語法
1[$first, $second, $third] = Process::concurrently(function (Pool $pool) {2 $pool->path(__DIR__)->command('ls -la');3 $pool->path(app_path())->command('ls -la');4 $pool->path(storage_path())->command('ls -la');5});6 7echo $first->output();
命名池程序
透過數值鍵存取程序池結果不是很直觀; 因此,Laravel 允許您透過 as
方法為池中的每個程序指派字串鍵。 此鍵也將傳遞給提供給 start
方法的閉包,讓您可以判斷輸出屬於哪個程序
1$pool = Process::pool(function (Pool $pool) { 2 $pool->as('first')->command('bash import-1.sh'); 3 $pool->as('second')->command('bash import-2.sh'); 4 $pool->as('third')->command('bash import-3.sh'); 5})->start(function (string $type, string $output, string $key) { 6 // ... 7}); 8 9$results = $pool->wait();10 11return $results['first']->output();
池程序 ID 和訊號
由於程序池的 running
方法提供了池中所有調用程序的集合,因此您可以輕鬆存取基礎池程序 ID
1$processIds = $pool->running()->each->id();
並且,為了方便起見,您可以在程序池上調用 signal
方法,以將訊號傳送到池中的每個程序
1$pool->signal(SIGUSR2);
測試
許多 Laravel 服務都提供了功能,可協助您輕鬆且富有表現力地編寫測試,而 Laravel 的程序服務也不例外。 Process
facade 的 fake
方法允許您指示 Laravel 在調用程序時傳回 Stubbed / 虛擬結果。
偽造程序
為了探索 Laravel 偽造程序的能力,讓我們想像一個調用程序的路由
1use Illuminate\Support\Facades\Process;2use Illuminate\Support\Facades\Route;3 4Route::get('/import', function () {5 Process::run('bash import.sh');6 7 return 'Import complete!';8});
在測試此路由時,我們可以指示 Laravel 為每個調用的程序傳回虛假的成功程序結果,方法是在沒有引數的情況下調用 Process
facade 上的 fake
方法。 此外,我們甚至可以斷言給定的程序已「執行」
1<?php 2 3use Illuminate\Process\PendingProcess; 4use Illuminate\Contracts\Process\ProcessResult; 5use Illuminate\Support\Facades\Process; 6 7test('process is invoked', function () { 8 Process::fake(); 9 10 $response = $this->get('/import');11 12 // Simple process assertion...13 Process::assertRan('bash import.sh');14 15 // Or, inspecting the process configuration...16 Process::assertRan(function (PendingProcess $process, ProcessResult $result) {17 return $process->command === 'bash import.sh' &&18 $process->timeout === 60;19 });20});
1<?php 2 3namespace Tests\Feature; 4 5use Illuminate\Process\PendingProcess; 6use Illuminate\Contracts\Process\ProcessResult; 7use Illuminate\Support\Facades\Process; 8use Tests\TestCase; 9 10class ExampleTest extends TestCase11{12 public function test_process_is_invoked(): void13 {14 Process::fake();15 16 $response = $this->get('/import');17 18 // Simple process assertion...19 Process::assertRan('bash import.sh');20 21 // Or, inspecting the process configuration...22 Process::assertRan(function (PendingProcess $process, ProcessResult $result) {23 return $process->command === 'bash import.sh' &&24 $process->timeout === 60;25 });26 }27}
如討論所述,在 Process
facade 上調用 fake
方法將指示 Laravel 始終傳回沒有輸出的成功程序結果。 但是,您可以使用 Process
facade 的 result
方法輕鬆指定偽造程序的輸出和退出代碼
1Process::fake([2 '*' => Process::result(3 output: 'Test output',4 errorOutput: 'Test error output',5 exitCode: 1,6 ),7]);
偽造特定程序
您可能在先前的範例中注意到,Process
facade 允許您透過將陣列傳遞給 fake
方法來為每個程序指定不同的虛假結果。
陣列的鍵應表示您要偽造的命令模式及其相關結果。 *
字元可以用作萬用字元。 任何未偽造的程序命令都將實際調用。 您可以使用 Process
facade 的 result
方法為這些命令建構 Stub / 虛假結果
1Process::fake([2 'cat *' => Process::result(3 output: 'Test "cat" output',4 ),5 'ls *' => Process::result(6 output: 'Test "ls" output',7 ),8]);
如果您不需要自訂偽造程序的退出代碼或錯誤輸出,您可能會發現將偽造程序結果指定為簡單的字串更方便
1Process::fake([2 'cat *' => 'Test "cat" output',3 'ls *' => 'Test "ls" output',4]);
偽造程序序列
如果您正在測試的程式碼調用了多個具有相同命令的程序,您可能希望為每個程序調用指派不同的虛假程序結果。 您可以透過 Process
facade 的 sequence
方法來完成此操作
1Process::fake([2 'ls *' => Process::sequence()3 ->push(Process::result('First invocation'))4 ->push(Process::result('Second invocation')),5]);
偽造異步程序生命週期
到目前為止,我們主要討論了使用 run
方法同步調用的偽造程序。 但是,如果您嘗試測試與透過 start
調用的異步程序互動的程式碼,您可能需要更複雜的方法來描述您的偽造程序。
例如,讓我們想像以下與異步程序互動的路由
1use Illuminate\Support\Facades\Log; 2use Illuminate\Support\Facades\Route; 3 4Route::get('/import', function () { 5 $process = Process::start('bash import.sh'); 6 7 while ($process->running()) { 8 Log::info($process->latestOutput()); 9 Log::info($process->latestErrorOutput());10 }11 12 return 'Done';13});
為了正確地偽造此程序,我們需要能夠描述 running
方法應該傳回 true
的次數。 此外,我們可能想要指定應按順序傳回的多行輸出。 若要完成此操作,我們可以使用 Process
facade 的 describe
方法
1Process::fake([2 'bash import.sh' => Process::describe()3 ->output('First line of standard output')4 ->errorOutput('First line of error output')5 ->output('Second line of standard output')6 ->exitCode(0)7 ->iterations(3),8]);
讓我們深入探討上面的範例。 使用 output
和 errorOutput
方法,我們可以指定將按順序傳回的多行輸出。 exitCode
方法可用於指定偽造程序的最終退出代碼。 最後,iterations
方法可用於指定 running
方法應傳回 true
的次數。
可用的斷言
如先前討論,Laravel 為您的功能測試提供了多個程序斷言。 我們將在下面討論這些斷言中的每一個。
assertRan
斷言已調用給定的程序
1use Illuminate\Support\Facades\Process;2 3Process::assertRan('ls -la');
assertRan
方法也接受閉包,該閉包將接收程序和程序結果的實例,讓您可以檢查程序的已配置選項。 如果此閉包傳回 true
,則斷言將「通過」
1Process::assertRan(fn ($process, $result) =>2 $process->command === 'ls -la' &&3 $process->path === __DIR__ &&4 $process->timeout === 605);
傳遞給 assertRan
閉包的 $process
是 Illuminate\Process\PendingProcess
的實例,而 $result
是 Illuminate\Contracts\Process\ProcessResult
的實例。
assertDidntRun
斷言未調用給定的程序
1use Illuminate\Support\Facades\Process;2 3Process::assertDidntRun('ls -la');
與 assertRan
方法類似,assertDidntRun
方法也接受閉包,該閉包將接收程序和程序結果的實例,讓您可以檢查程序的已配置選項。 如果此閉包傳回 true
,則斷言將「失敗」
1Process::assertDidntRun(fn (PendingProcess $process, ProcessResult $result) =>2 $process->command === 'ls -la'3);
assertRanTimes
斷言給定的程序被調用了給定的次數
1use Illuminate\Support\Facades\Process;2 3Process::assertRanTimes('ls -la', times: 3);
assertRanTimes
方法也接受閉包,該閉包將接收程序和程序結果的實例,讓您可以檢查程序的已配置選項。 如果此閉包傳回 true
且程序被調用了指定的次數,則斷言將「通過」
1Process::assertRanTimes(function (PendingProcess $process, ProcessResult $result) {2 return $process->command === 'ls -la';3}, times: 3);
防止 Stray 程序
如果您想確保在整個單獨測試或完整測試套件中偽造了所有調用的程序,您可以調用 preventStrayProcesses
方法。 調用此方法後,任何沒有對應偽造結果的程序都將拋出例外,而不是啟動實際程序
1use Illuminate\Support\Facades\Process; 2 3Process::preventStrayProcesses(); 4 5Process::fake([ 6 'ls *' => 'Test output...', 7]); 8 9// Fake response is returned...10Process::run('ls -la');11 12// An exception is thrown...13Process::run('bash import.sh');