プロセス
はじめに
LaravelはSymfony Processコンポーネント をベースにした、表現力豊かで最小限のAPIを提供しており、Laravelアプリケーションから外部プロセスを簡単に呼び出すことができます。Laravelのプロセス機能は、最も一般的なユースケースと優れた開発者エクスペリエンスに焦点を当てています。
プロセスの呼び出し
プロセスを呼び出すには、Process
ファサードによって提供されるrun
メソッドとstart
メソッドを使用できます。run
メソッドはプロセスを呼び出し、プロセスの実行が完了するまで待機します。一方、start
メソッドは非同期プロセスの実行に使用されます。このドキュメントでは、両方の方法を調べます。まず、基本的な同期プロセスを呼び出し、その結果を検査する方法を見てみましょう。
use Illuminate\Support\Facades\Process; $result = Process::run('ls -la'); return $result->output();
もちろん、run
メソッドによって返されるIlluminate\Contracts\Process\ProcessResult
インスタンスは、プロセスの結果を検査するために使用できるさまざまな便利なメソッドを提供します。
$result = Process::run('ls -la'); $result->successful();$result->failed();$result->exitCode();$result->output();$result->errorOutput();
例外の送出
プロセス結果があり、終了コードが0より大きい場合(つまり失敗を示す場合)にIlluminate\Process\Exceptions\ProcessFailedException
のインスタンスを送出したい場合は、throw
メソッドとthrowIf
メソッドを使用できます。プロセスが失敗しなかった場合、プロセス結果インスタンスが返されます。
$result = Process::run('ls -la')->throw(); $result = Process::run('ls -la')->throwIf($condition);
プロセスのオプション
もちろん、プロセスを呼び出す前に、その動作をカスタマイズする必要があるかもしれません。幸いにも、Laravelでは、作業ディレクトリ、タイムアウト、環境変数など、さまざまなプロセス機能を調整できます。
作業ディレクトリパス
path
メソッドを使用して、プロセスの作業ディレクトリを指定できます。このメソッドが呼び出されない場合、プロセスは現在実行中のPHPスクリプトの作業ディレクトリを継承します。
$result = Process::path(__DIR__)->run('ls -la');
入力
input
メソッドを使用して、「標準入力」を介してプロセスに入力を提供できます。
$result = Process::input('Hello World')->run('cat');
タイムアウト
デフォルトでは、プロセスは60秒以上実行された後、Illuminate\Process\Exceptions\ProcessTimedOutException
のインスタンスを送出します。ただし、timeout
メソッドを使用してこの動作をカスタマイズできます。
$result = Process::timeout(120)->run('bash import.sh');
または、プロセスのタイムアウトを完全に無効にするには、forever
メソッドを呼び出すことができます。
$result = Process::forever()->run('bash import.sh');
idleTimeout
メソッドを使用して、プロセスが出力せずに実行できる最大秒数を指定できます。
$result = Process::timeout(60)->idleTimeout(30)->run('bash import.sh');
環境変数
env
メソッドを使用して、プロセスに環境変数を提供できます。呼び出されたプロセスは、システムによって定義されたすべての環境変数も継承します。
$result = Process::forever() ->env(['IMPORT_PATH' => __DIR__]) ->run('bash import.sh');
継承された環境変数を呼び出されたプロセスから削除する場合は、その環境変数にfalse
の値を指定できます。
$result = Process::forever() ->env(['LOAD_PATH' => false]) ->run('bash import.sh');
TTYモード
tty
メソッドを使用して、プロセスのTTYモードを有効にできます。TTYモードは、プロセスの入出力とプログラムの入出力を接続し、プロセスとしてVimやNanoなどのエディターを開くことができます。
Process::forever()->tty()->run('vim');
プロセスの出力
前述のように、プロセスの出力は、プロセス結果のoutput
(stdout)とerrorOutput
(stderr)メソッドを使用してアクセスできます。
use Illuminate\Support\Facades\Process; $result = Process::run('ls -la'); echo $result->output();echo $result->errorOutput();
ただし、run
メソッドの第2引数としてクロージャを渡すことで、リアルタイムで出力も収集できます。クロージャは、出力の「タイプ」(stdout
またはstderr
)と出力文字列そのものの2つの引数を受け取ります。
$result = Process::run('ls -la', function (string $type, string $output) { echo $output;});
LaravelはseeInOutput
メソッドとseeInErrorOutput
メソッドも提供しており、特定の文字列がプロセスの出力に含まれているかどうかを簡単に確認できます。
if (Process::run('ls -la')->seeInOutput('laravel')) { // ...}
プロセスの出力の無効化
関心のない大量の出力をプロセスが出力している場合、出力を完全に取得しないことでメモリを節約できます。これを行うには、プロセスを構築する際にquietly
メソッドを呼び出します。
use Illuminate\Support\Facades\Process; $result = Process::quietly()->run('bash import.sh');
パイプライン
あるプロセスの出力を別のプロセスの入力にする必要がある場合があります。これは、プロセスの出力を別のプロセスに「パイプする」と呼ばれることがよくあります。Process
ファサードによって提供されるpipe
メソッドを使用すると、これを簡単に実現できます。pipe
メソッドはパイプされたプロセスを同期的に実行し、パイプライン内の最後のプロセスのプロセス結果を返します。
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()) { // ...}
パイプラインを構成する個々のプロセスをカスタマイズする必要がない場合は、コマンド文字列の配列をpipe
メソッドに渡すだけです。
$result = Process::pipe([ 'cat example.txt', 'grep -i "laravel"',]);
pipe
メソッドにクロージャを第2引数として渡すことで、プロセスの出力をリアルタイムで収集できます。クロージャは、出力の「タイプ」(stdout
またはstderr
)と出力文字列そのものの2つの引数を受け取ります。
$result = Process::pipe(function (Pipe $pipe) { $pipe->command('cat example.txt'); $pipe->command('grep -i "laravel"');}, function (string $type, string $output) { echo $output;});
Laravelでは、as
メソッドを使用して、パイプライン内の各プロセスに文字列キーを割り当てることもできます。このキーは、pipe
メソッドに提供された出力クロージャにも渡され、出力のプロセスを特定できます。
$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) { // ...});
非同期プロセス
run
メソッドはプロセスを同期的に呼び出しますが、start
メソッドを使用して非同期的にプロセスを呼び出すことができます。これにより、アプリケーションはバックグラウンドでプロセスが実行されている間も、他のタスクを実行し続けることができます。プロセスが呼び出された後、running
メソッドを使用して、プロセスがまだ実行中かどうかを確認できます。
$process = Process::timeout(120)->start('bash import.sh'); while ($process->running()) { // ...} $result = $process->wait();
お気づきかもしれませんが、wait
メソッドを呼び出して、プロセスが実行を終了するまで待機し、プロセス結果インスタンスを取得できます。
$process = Process::timeout(120)->start('bash import.sh'); // ... $result = $process->wait();
プロセスIDとシグナル
id
メソッドを使用して、実行中のプロセスのオペレーティングシステムによって割り当てられたプロセスIDを取得できます。
$process = Process::start('bash import.sh'); return $process->id();
signal
メソッドを使用して、実行中のプロセスに「シグナル」を送信できます。事前に定義されたシグナル定数のリストは、PHPドキュメント にあります。
$process->signal(SIGUSR2);
非同期プロセスの出力
非同期プロセスが実行されている間、output
メソッドと errorOutput
メソッドを使用して、その現在の出力全体にアクセスできます。ただし、最後に取得されてから発生したプロセスの出力にアクセスするには、latestOutput
と latestErrorOutput
を使用できます。
$process = Process::timeout(120)->start('bash import.sh'); while ($process->running()) { echo $process->latestOutput(); echo $process->latestErrorOutput(); sleep(1);}
run
メソッドと同様に、start
メソッドの第2引数としてクロージャを渡すことで、非同期プロセスからリアルタイムで出力を収集することもできます。クロージャは、「type」(stdout
または stderr
)と出力文字列自体の2つの引数を受け取ります。
$process = Process::start('bash import.sh', function (string $type, string $output) { echo $output;}); $result = $process->wait();
プロセスが終了するまで待つ代わりに、waitUntil
メソッドを使用して、プロセスの出力に基づいて待機を停止できます。Laravel は、waitUntil
メソッドに渡されたクロージャが true
を返すときに、プロセスの終了待ちを停止します。
$process = Process::start('bash import.sh'); $process->waitUntil(function (string $type, string $output) { return $output === 'Ready...';});
同時実行プロセス
Laravel は、多数の同時実行非同期プロセスを簡単に管理できるプールも提供しており、多くのタスクを同時に簡単に実行できます。開始するには、Illuminate\Process\Pool
のインスタンスを受け取るクロージャを受け取る pool
メソッドを呼び出します。
このクロージャ内では、プールに属するプロセスを定義できます。start
メソッドでプロセスプールが開始されると、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();
ご覧のとおり、wait
メソッドを使用して、すべてのプールプロセスの実行が完了し、その結果が解決されるのを待つことができます。wait
メソッドは、配列アクセス可能なオブジェクトを返し、プール内の各プロセスのプロセス結果インスタンスにキーでアクセスできます。
$results = $pool->wait(); echo $results[0]->output();
または、便宜上、concurrently
メソッドを使用して非同期プロセスプールを開始し、その結果をすぐに待機できます。これは、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();
プールプロセスの命名
数値キーによるプロセスプール結果へのアクセスは、あまり表現力が高くないため、Laravel では、as
メソッドを使用してプール内の各プロセスに文字列キーを割り当てることができます。このキーは、start
メソッドに提供されたクロージャにも渡され、どのプロセスの出力であるかを判断できます。
$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とシグナル
プロセスプールの running
メソッドはプール内のすべての呼び出されたプロセスをコレクションとして提供するため、基礎となるプールのプロセスIDに簡単にアクセスできます。
$processIds = $pool->running()->each->id();
そして、便宜上、プロセスプールで signal
メソッドを呼び出して、プール内のすべてのプロセスにシグナルを送信できます。
$pool->signal(SIGUSR2);
テスト
多くの Laravel サービスは、テストを簡単かつ表現力豊かに記述する機能を提供しており、Laravel のプロセスサービスも例外ではありません。Process
ファサードの fake
メソッドを使用すると、プロセスが呼び出されたときにスタブ/ダミーの結果を返すように Laravel に指示できます。
プロセスのフェイク
Laravel のプロセスをフェイクする機能を調べるために、プロセスを呼び出すルートを想像してみましょう。
use Illuminate\Support\Facades\Process;use Illuminate\Support\Facades\Route; Route::get('/import', function () { Process::run('bash import.sh'); return 'Import complete!';});
このルートをテストするときは、引数なしで Process
ファサードで fake
メソッドを呼び出すことで、呼び出されたすべてのプロセスに対して偽の成功したプロセス結果を返すように Laravel に指示できます。さらに、指定されたプロセスが「実行」されたことを アサート することさえできます。
<?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; }); }}
説明したように、Process
ファサードで fake
メソッドを呼び出すと、Laravel は常に出力のない成功したプロセス結果を返します。ただし、Process
ファサードの result
メソッドを使用して、フェイクプロセスの出力と終了コードを簡単に指定できます。
Process::fake([ '*' => Process::result( output: 'Test output', errorOutput: 'Test error output', exitCode: 1, ),]);
特定のプロセスのフェイク
前の例で気づかれたかもしれませんが、Process
ファサードでは、配列を fake
メソッドに渡すことで、プロセスごとに異なる偽の結果を指定できます。
配列のキーは、フェイクするコマンドパターンとその関連する結果を表す必要があります。*
文字はワイルドカード文字として使用できます。フェイクされていないプロセスコマンドはすべて実際に呼び出されます。Process
ファサードの result
メソッドを使用して、これらのコマンドのスタブ/フェイク結果を作成できます。
Process::fake([ 'cat *' => Process::result( output: 'Test "cat" output', ), 'ls *' => Process::result( output: 'Test "ls" output', ),]);
フェイクプロセスの終了コードやエラー出力をカスタマイズする必要がない場合は、フェイクプロセス結果を単純な文字列として指定する方が便利だと感じるかもしれません。
Process::fake([ 'cat *' => 'Test "cat" output', 'ls *' => 'Test "ls" output',]);
プロセスシーケンスのフェイク
テスト中のコードが同じコマンドで複数のプロセスを呼び出す場合、各プロセスの呼び出しに異なるフェイクプロセス結果を割り当てたい場合があります。これは、Process
ファサードの sequence
メソッドを使用して実現できます。
Process::fake([ 'ls *' => Process::sequence() ->push(Process::result('First invocation')) ->push(Process::result('Second invocation')),]);
非同期プロセスのライフサイクルのフェイク
これまで、主に run
メソッドを使用して同期的に呼び出されるプロセスのフェイクについて説明してきました。ただし、start
を介して呼び出された非同期プロセスと対話するコードをテストしようとしている場合は、フェイクプロセスを記述するためのより高度なアプローチが必要になる場合があります。
たとえば、非同期プロセスと対話する次のルートを考えてみましょう。
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';});
このプロセスを適切にフェイクするには、running
メソッドが何回 true
を返すかを記述できる必要があります。さらに、順番に返される複数の出力行を指定したい場合があります。これを実現するには、Process
ファサードの describe
メソッドを使用できます。
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),]);
上記の例を見てみましょう。output
メソッドと errorOutput
メソッドを使用して、順番に返される複数の出力行を指定できます。exitCode
メソッドを使用して、フェイクプロセスの最終的な終了コードを指定できます。最後に、iterations
メソッドを使用して、running
メソッドが何回 true
を返すかを指定できます。
利用可能なアサーション
前述のように、Laravel は機能テストのためにいくつかのプロセスアサーションを提供しています。これらの各アサーションについて以下で説明します。
assertRan
指定されたプロセスが呼び出されたことをアサートします。
use Illuminate\Support\Facades\Process; Process::assertRan('ls -la');
assertRan
メソッドは、クロージャも受け入れます。これは、プロセスインスタンスとプロセス結果を受け取り、プロセスの構成済みオプションを検査できます。このクロージャが true
を返す場合、アサーションは「成功」します。
Process::assertRan(fn ($process, $result) => $process->command === 'ls -la' && $process->path === __DIR__ && $process->timeout === 60);
assertRan
クロージャに渡される $process
は Illuminate\Process\PendingProcess
のインスタンスであり、$result
は Illuminate\Contracts\Process\ProcessResult
のインスタンスです。
assertDidntRun
指定されたプロセスが呼び出されなかったことをアサートします。
use Illuminate\Support\Facades\Process; Process::assertDidntRun('ls -la');
assertRan
メソッドと同様に、assertDidntRun
メソッドもクロージャを受け入れます。これは、プロセスインスタンスとプロセス結果を受け取り、プロセスの構成済みオプションを検査できます。このクロージャが true
を返す場合、アサーションは「失敗」します。
Process::assertDidntRun(fn (PendingProcess $process, ProcessResult $result) => $process->command === 'ls -la');
assertRanTimes
指定されたプロセスが指定された回数呼び出されたことをアサートします。
use Illuminate\Support\Facades\Process; Process::assertRanTimes('ls -la', times: 3);
assertRanTimes
メソッドは、クロージャも受け入れます。これは、プロセスインスタンスとプロセス結果を受け取り、プロセスの構成済みオプションを検査できます。このクロージャが true
を返し、プロセスが指定された回数呼び出された場合、アサーションは「成功」します。
Process::assertRanTimes(function (PendingProcess $process, ProcessResult $result) { return $process->command === 'ls -la';}, times: 3);
不要なプロセスの防止
個々のテストまたは完全なテストスイート全体で呼び出されたすべてのプロセスがフェイクされていることを確認したい場合は、preventStrayProcesses
メソッドを呼び出すことができます。このメソッドを呼び出した後、対応するフェイク結果がないプロセスは、実際のプロセスを開始するのではなく、例外をスローします。
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');