はじめに
Laravelは、Symfony Processコンポーネントの周りに表現力豊かで最小限のAPIを提供し、Laravelアプリケーションから外部プロセスを便利に呼び出すことができます。Laravelのプロセス機能は、最も一般的なユースケースと素晴らしい開発者体験に焦点を当てています。
プロセスの呼び出し
プロセスを呼び出すには、run
およびstart
メソッドをProcess
ファサードから使用できます。run
メソッドはプロセスを呼び出し、プロセスが実行を終了するのを待ちます。一方、start
メソッドは非同期プロセス実行に使用されます。このドキュメント内で両方のアプローチを検討します。まず、基本的な同期プロセスを呼び出し、その結果を確認する方法を見てみましょう:
use Illuminate\Support\Facades\Process;
$result = Process::run('ls -la');
return $result->output();
もちろん、Illuminate\Contracts\Process\ProcessResult
インスタンスはrun
メソッドによって返され、プロセス結果を検査するために使用できるさまざまな便利なメソッドを提供します:
$result = Process::run('ls -la');
$result->successful();
$result->failed();
$result->exitCode();
$result->output();
$result->errorOutput();
例外のスロー
プロセス結果があり、終了コードがゼロより大きい場合(つまり、失敗を示す)に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');
``````php
$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モード
``````php
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');
パイプライン
時には、1つのプロセスの出力を別のプロセスの入力にしたい場合があります。これはしばしば、プロセスの出力を別のプロセスに「パイプする」と呼ばれます。pipe
メソッドはProcess
ファサードによって提供され、これを簡単に実現します。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) {
// ...
});
非同期プロセス
``````php
$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とシグナル
``````php
$process = Process::start('bash import.sh');
return $process->id();
`
``````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);
}
``````php
$process = Process::start('bash import.sh', function (string $type, string $output) {
echo $output;
});
$result = $process->wait();
`
同時プロセス
Laravelは、同時に非同期プロセスのプールを管理するのを簡単にし、多くのタスクを同時に実行できるようにします。始めるには、pool
メソッドを呼び出し、Illuminate\Process\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!';
});
このルートをテストする際、fake
メソッドをProcess
ファサードに引数なしで呼び出すことで、呼び出されたすべてのプロセスに対してフェイクの成功したプロセス結果を返すよう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;
});
}
}
前述のように、fake
メソッドをProcess
ファサードに呼び出すことで、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');
``````php
Process::assertRan(fn ($process, $result) =>
$process->command === 'ls -la' &&
$process->path === __DIR__ &&
$process->timeout === 60
);
`
<a name="assert-process-didnt-run"></a>
#### assertDidntRun
特定のプロセスが呼び出されなかったことを確認します:
``````php
use Illuminate\Support\Facades\Process;
Process::assertDidntRun('ls -la');
`
``````php
Process::assertDidntRun(fn (PendingProcess $process, ProcessResult $result) =>
$process->command === 'ls -la'
);
`
assertRanTimes
特定のプロセスが指定された回数呼び出されたことを確認します:
use Illuminate\Support\Facades\Process;
Process::assertRanTimes('ls -la', times: 3);
``````php
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');