コンテンツへスキップ

プロセス

はじめに

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 メソッドを使用して、その現在の出力全体にアクセスできます。ただし、最後に取得されてから発生したプロセスの出力にアクセスするには、latestOutputlatestErrorOutput を使用できます。

$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 クロージャに渡される $processIlluminate\Process\PendingProcess のインスタンスであり、$resultIlluminate\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');