はじめに

Laravelは、Symfony Processコンポーネントの周りに表現力豊かで最小限のAPIを提供し、Laravelアプリケーションから外部プロセスを便利に呼び出すことができます。Laravelのプロセス機能は、最も一般的なユースケースと素晴らしい開発者体験に焦点を当てています。

プロセスの呼び出し

プロセスを呼び出すには、runおよびstartメソッドをProcessファサードから使用できます。runメソッドはプロセスを呼び出し、プロセスが実行を終了するのを待ちます。一方、startメソッドは非同期プロセス実行に使用されます。このドキュメント内で両方のアプローチを検討します。まず、基本的な同期プロセスを呼び出し、その結果を確認する方法を見てみましょう:

  1. use Illuminate\Support\Facades\Process;
  2. $result = Process::run('ls -la');
  3. return $result->output();

もちろん、Illuminate\Contracts\Process\ProcessResultインスタンスはrunメソッドによって返され、プロセス結果を検査するために使用できるさまざまな便利なメソッドを提供します:

  1. $result = Process::run('ls -la');
  2. $result->successful();
  3. $result->failed();
  4. $result->exitCode();
  5. $result->output();
  6. $result->errorOutput();

例外のスロー

プロセス結果があり、終了コードがゼロより大きい場合(つまり、失敗を示す)にIlluminate\Process\Exceptions\ProcessFailedExceptionのインスタンスをスローしたい場合は、throwおよびthrowIfメソッドを使用できます。プロセスが失敗しなかった場合、プロセス結果インスタンスが返されます:

  1. $result = Process::run('ls -la')->throw();
  2. $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');
  1. ``````php
  2. $result = Process::timeout(60)->idleTimeout(30)->run('bash import.sh');
  3. `

環境変数

環境変数は、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モード

  1. ``````php
  2. Process::forever()->tty()->run('vim');
  3. `

プロセス出力

前述のように、プロセス出力はプロセス結果のoutput(stdout)およびerrorOutput(stderr)メソッドを使用してアクセスできます:

  1. use Illuminate\Support\Facades\Process;
  2. $result = Process::run('ls -la');
  3. echo $result->output();
  4. echo $result->errorOutput();

ただし、出力はrunメソッドに2番目の引数としてクロージャを渡すことでリアルタイムで収集することもできます。クロージャは、出力の「タイプ」(stdoutまたはstderr)と出力文字列自体の2つの引数を受け取ります:

  1. $result = Process::run('ls -la', function (string $type, string $output) {
  2. echo $output;
  3. });

Laravelはまた、seeInOutputおよびseeInErrorOutputメソッドを提供しており、特定の文字列がプロセスの出力に含まれているかどうかを判断する便利な方法を提供します:

  1. if (Process::run('ls -la')->seeInOutput('laravel')) {
  2. // ...
  3. }

プロセス出力の無効化

プロセスが興味のない大量の出力を書き込んでいる場合、出力取得を完全に無効にすることでメモリを節約できます。これを実現するには、プロセスを構築する際にquietlyメソッドを呼び出します:

  1. use Illuminate\Support\Facades\Process;
  2. $result = Process::quietly()->run('bash import.sh');

パイプライン

時には、1つのプロセスの出力を別のプロセスの入力にしたい場合があります。これはしばしば、プロセスの出力を別のプロセスに「パイプする」と呼ばれます。pipeメソッドはProcessファサードによって提供され、これを簡単に実現します。pipeメソッドは、パイプされたプロセスを同期的に実行し、パイプライン内の最後のプロセスのプロセス結果を返します:

  1. use Illuminate\Process\Pipe;
  2. use Illuminate\Support\Facades\Process;
  3. $result = Process::pipe(function (Pipe $pipe) {
  4. $pipe->command('cat example.txt');
  5. $pipe->command('grep -i "laravel"');
  6. });
  7. if ($result->successful()) {
  8. // ...
  9. }

個々のプロセスをカスタマイズする必要がない場合は、pipeメソッドにコマンド文字列の配列を単に渡すことができます:

  1. $result = Process::pipe([
  2. 'cat example.txt',
  3. 'grep -i "laravel"',
  4. ]);

プロセス出力は、pipeメソッドに2番目の引数としてクロージャを渡すことでリアルタイムで収集できます。クロージャは、出力の「タイプ」(stdoutまたはstderr)と出力文字列自体の2つの引数を受け取ります:

  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. });

非同期プロセス

  1. ``````php
  2. $process = Process::timeout(120)->start('bash import.sh');
  3. while ($process->running()) {
  4. // ...
  5. }
  6. $result = $process->wait();
  7. `

ご覧のとおり、waitメソッドを呼び出してプロセスが実行を終了するまで待ち、プロセス結果インスタンスを取得できます:

  1. $process = Process::timeout(120)->start('bash import.sh');
  2. // ...
  3. $result = $process->wait();

プロセスIDとシグナル

  1. ``````php
  2. $process = Process::start('bash import.sh');
  3. return $process->id();
  4. `
  1. ``````php
  2. $process->signal(SIGUSR2);
  3. `

非同期プロセス出力

非同期プロセスが実行中の間、outputおよびerrorOutputメソッドを使用してその現在の出力全体にアクセスできます。ただし、latestOutputおよびlatestErrorOutputを使用して、出力が最後に取得されてから発生したプロセスの出力にアクセスできます:

  1. $process = Process::timeout(120)->start('bash import.sh');
  2. while ($process->running()) {
  3. echo $process->latestOutput();
  4. echo $process->latestErrorOutput();
  5. sleep(1);
  6. }
  1. ``````php
  2. $process = Process::start('bash import.sh', function (string $type, string $output) {
  3. echo $output;
  4. });
  5. $result = $process->wait();
  6. `

同時プロセス

Laravelは、同時に非同期プロセスのプールを管理するのを簡単にし、多くのタスクを同時に実行できるようにします。始めるには、poolメソッドを呼び出し、Illuminate\Process\Poolのインスタンスを受け取るクロージャを受け入れます。

このクロージャ内で、プールに属するプロセスを定義できます。startメソッドを介してプロセスプールが開始されると、runningメソッドを介して実行中のプロセスのコレクションにアクセスできます:

  1. use Illuminate\Process\Pool;
  2. use Illuminate\Support\Facades\Process;
  3. $pool = Process::pool(function (Pool $pool) {
  4. $pool->path(__DIR__)->command('bash import-1.sh');
  5. $pool->path(__DIR__)->command('bash import-2.sh');
  6. $pool->path(__DIR__)->command('bash import-3.sh');
  7. })->start(function (string $type, string $output, int $key) {
  8. // ...
  9. });
  10. while ($pool->running()->isNotEmpty()) {
  11. // ...
  12. }
  13. $results = $pool->wait();

ご覧のとおり、プール内のすべてのプロセスが実行を終了するのを待ち、waitメソッドを介してその結果を解決できます。waitメソッドは、プール内の各プロセスのプロセス結果インスタンスにそのキーでアクセスできる配列アクセス可能オブジェクトを返します:

  1. $results = $pool->wait();
  2. echo $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. echo $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. $results = $pool->wait();
  9. return $results['first']->output();

プールプロセスIDとシグナル

プロセスプールのrunningメソッドは、プール内のすべての呼び出されたプロセスのコレクションを提供するため、基盤となるプールプロセスIDに簡単にアクセスできます:

  1. $processIds = $pool->running()->each->id();

便利なことに、プロセスプールにsignalメソッドを呼び出して、プール内のすべてのプロセスにシグナルを送信できます:

  1. $pool->signal(SIGUSR2);

テスト

多くのLaravelサービスは、テストを簡単かつ表現力豊かに記述するのに役立つ機能を提供しており、Laravelのプロセスサービスも例外ではありません。Processファサードのfakeメソッドを使用すると、プロセスが呼び出されたときにLaravelにスタブ/ダミー結果を返すよう指示できます。

プロセスのフェイク

Laravelのプロセスをフェイクする能力を探るために、プロセスを呼び出すルートを想像してみましょう:

  1. use Illuminate\Support\Facades\Process;
  2. use Illuminate\Support\Facades\Route;
  3. Route::get('/import', function () {
  4. Process::run('bash import.sh');
  5. return 'Import complete!';
  6. });

このルートをテストする際、fakeメソッドをProcessファサードに引数なしで呼び出すことで、呼び出されたすべてのプロセスに対してフェイクの成功したプロセス結果を返すようLaravelに指示できます。さらに、特定のプロセスが「実行された」ことを主張することもできます:

  1. <?php
  2. use Illuminate\Process\PendingProcess;
  3. use Illuminate\Contracts\Process\ProcessResult;
  4. use Illuminate\Support\Facades\Process;
  5. test('process is invoked', function () {
  6. Process::fake();
  7. $response = $this->get('/import');
  8. // Simple process assertion...
  9. Process::assertRan('bash import.sh');
  10. // Or, inspecting the process configuration...
  11. Process::assertRan(function (PendingProcess $process, ProcessResult $result) {
  12. return $process->command === 'bash import.sh' &&
  13. $process->timeout === 60;
  14. });
  15. });
  1. <?php
  2. namespace Tests\Feature;
  3. use Illuminate\Process\PendingProcess;
  4. use Illuminate\Contracts\Process\ProcessResult;
  5. use Illuminate\Support\Facades\Process;
  6. use Tests\TestCase;
  7. class ExampleTest extends TestCase
  8. {
  9. public function test_process_is_invoked(): void
  10. {
  11. Process::fake();
  12. $response = $this->get('/import');
  13. // Simple process assertion...
  14. Process::assertRan('bash import.sh');
  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. }
  21. }

前述のように、fakeメソッドをProcessファサードに呼び出すことで、Laravelに常に出力なしで成功したプロセス結果を返すよう指示します。ただし、Processファサードのresultメソッドを使用して、フェイクプロセスの出力と終了コードを簡単に指定できます:

  1. Process::fake([
  2. '*' => Process::result(
  3. output: 'Test output',
  4. errorOutput: 'Test error output',
  5. exitCode: 1,
  6. ),
  7. ]);

特定のプロセスのフェイク

前の例で気づいたように、Processファサードは、fakeメソッドに配列を渡すことで、プロセスごとに異なるフェイク結果を指定することを許可します。

配列のキーは、フェイクしたいコマンドパターンとその関連結果を表す必要があります。*文字はワイルドカード文字として使用できます。フェイクされていないプロセスコマンドは実際に呼び出されます。Processファサードのresultメソッドを使用して、これらのコマンドのスタブ/フェイク結果を構築できます:

  1. Process::fake([
  2. 'cat *' => Process::result(
  3. output: 'Test "cat" output',
  4. ),
  5. 'ls *' => Process::result(
  6. output: 'Test "ls" output',
  7. ),
  8. ]);

フェイクプロセスの終了コードやエラー出力をカスタマイズする必要がない場合は、フェイクプロセス結果を単純な文字列として指定する方が便利です:

  1. Process::fake([
  2. 'cat *' => 'Test "cat" output',
  3. 'ls *' => 'Test "ls" output',
  4. ]);

プロセスシーケンスのフェイク

テストしているコードが同じコマンドで複数のプロセスを呼び出す場合、各プロセス呼び出しに異なるフェイクプロセス結果を割り当てたい場合があります。これをProcessファサードのsequenceメソッドを介して実現できます:

  1. Process::fake([
  2. 'ls *' => Process::sequence()
  3. ->push(Process::result('First invocation'))
  4. ->push(Process::result('Second invocation')),
  5. ]);

非同期プロセスライフサイクルのフェイク

これまで、runメソッドを使用して同期的に呼び出されるプロセスのフェイクについて主に議論してきました。ただし、startを介して呼び出される非同期プロセスと対話するコードをテストしようとしている場合、フェイクプロセスを記述するためにより洗練されたアプローチが必要になるかもしれません。

たとえば、非同期プロセスと対話する次のルートを想像してみましょう:

  1. use Illuminate\Support\Facades\Log;
  2. use Illuminate\Support\Facades\Route;
  3. Route::get('/import', function () {
  4. $process = Process::start('bash import.sh');
  5. while ($process->running()) {
  6. Log::info($process->latestOutput());
  7. Log::info($process->latestErrorOutput());
  8. }
  9. return 'Done';
  10. });

このプロセスを適切にフェイクするには、runningメソッドがtrueを返す回数を記述できる必要があります。さらに、シーケンスで返されるべき複数行の出力を指定したい場合があります。これを実現するために、Processファサードのdescribeメソッドを使用できます:

  1. Process::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

特定のプロセスが呼び出されたことを確認します:

  1. use Illuminate\Support\Facades\Process;
  2. Process::assertRan('ls -la');
  1. ``````php
  2. Process::assertRan(fn ($process, $result) =>
  3. $process->command === 'ls -la' &&
  4. $process->path === __DIR__ &&
  5. $process->timeout === 60
  6. );
  7. `
  1. <a name="assert-process-didnt-run"></a>
  2. #### assertDidntRun
  3. 特定のプロセスが呼び出されなかったことを確認します:
  4. ``````php
  5. use Illuminate\Support\Facades\Process;
  6. Process::assertDidntRun('ls -la');
  7. `
  1. ``````php
  2. Process::assertDidntRun(fn (PendingProcess $process, ProcessResult $result) =>
  3. $process->command === 'ls -la'
  4. );
  5. `

assertRanTimes

特定のプロセスが指定された回数呼び出されたことを確認します:

  1. use Illuminate\Support\Facades\Process;
  2. Process::assertRanTimes('ls -la', times: 3);
  1. ``````php
  2. Process::assertRanTimes(function (PendingProcess $process, ProcessResult $result) {
  3. return $process->command === 'ls -la';
  4. }, times: 3);
  5. `

迷惑なプロセスの防止

すべての呼び出されたプロセスが個々のテストまたは完全なテストスイート全体でフェイクされていることを確認したい場合は、preventStrayProcessesメソッドを呼び出すことができます。このメソッドを呼び出した後、対応するフェイク結果がないプロセスは、実際のプロセスを開始するのではなく、例外をスローします:

  1. use Illuminate\Support\Facades\Process;
  2. Process::preventStrayProcesses();
  3. Process::fake([
  4. 'ls *' => 'Test output...',
  5. ]);
  6. // Fake response is returned...
  7. Process::run('ls -la');
  8. // An exception is thrown...
  9. Process::run('bash import.sh');