Laravel 10.x プロセス

イントロダクション

LaravelはSymfonyプロセスコンポーネントを核にした、表現力豊かで最小限の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のインスタンスを投げたい場合は、throwthrowIfメソッドを使用してください。プロセスが失敗しなかった場合、プロセス結果のインスタンスを返します。

$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引数へクロージャを渡すと、リアルタイムに出力を収集することもできます。クロージャは2つの引数を取ります。出力の「タイプ」(stdoutまたはstderr)と出力文字列そのものです。

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

Laravelには、seeInOutputseeInErrorOutputメソッドもあり、指定文字列がプロセスの出力に含まれているかを判断できる、便利な方法を提供しています。

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引数へクロージャを渡すと、プロセスの出力をリアルタイムに収集できます。クロージャは2引数を取ります。出力の「タイプ」(stdoutstderr)と、出力文字列そのものです:

$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引数にクロージャを渡せば、非同期プロセスからリアルタイムに出力を収集できます。クロージャは2つの引数を受け取ります。出力の「タイプ」(stdoutまたはstderr)と出力文字列そのものです。

$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へスタブ/ダミーの結果を返すように指示できます。

プロセスのFake

LaravelのプロセスFake機能を調べるため、プロセスを起動するあるルートを想像してみましょう。

use Illuminate\Support\Facades\Process;
use Illuminate\Support\Facades\Route;

Route::get('/import', function () {
    Process::run('bash import.sh');

    return 'Import complete!';
});

このルートをテストする場合、引数なしでProcessファサードのfakeメソッドを呼び出すことで、起動したすべてのプロセスに対し、プロセス実行成功のFakeな結果を返すように、Laravelへ指示できます。さらに、与えられたプロセスが「実行済み(run)」であることをアサートすることもできます。

<?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('/');

        // シンプルなプロセスのアサート
        Process::assertRan('bash import.sh');

        // もしくは、プロセス設定を調べる
        Process::assertRan(function (PendingProcess $process, ProcessResult $result) {
            return $process->command === 'bash import.sh' &&
                   $process->timeout === 60;
        });
    }
}

説明してきたように、Processファサードのfakeメソッドを呼び出すと、Laravelは常にプロセス実行成功の結果を返すように指示し、出力は返しません。しかし、Processファサードのresultメソッドを使用すると、Fakeしたプロセスの出力と終了コードを簡単に指定できます。

Process::fake([
    '*' => Process::result(
        output: 'Test output',
        errorOutput: 'Test error output',
        exitCode: 1,
    ),
]);

特定プロセスのFake

前の例でお気づきかもしれませんが、Processファサードのfakeメソッドへ配列を渡すことで、プロセスごとに異なるFakeの結果を指定できます。

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

Process::fake([
    'cat *' => Process::result(
        output: 'Test "cat" output',
    ),
    'ls *' => Process::result(
        output: 'Test "ls" output',
    ),
]);

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

Process::fake([
    'cat *' => 'Test "cat" output',
    'ls *' => 'Test "ls" output',
]);

プロセス順序のFake

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

Process::fake([
    'ls *' => Process::sequence()
                ->push(Process::result('First invocation'))
                ->push(Process::result('Second invocation')),
]);

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

ここまでは主に、runメソッドを使用して、同期的に起動するプロセスのフェイクについて説明してきました。しかし、もしstartを使って呼び出す非同期処理とやりとりするコードをテストしようとするなら、Fakeの処理を記述するためにもっと洗練されたアプローチが必要になるでしょう。

例として、非同期処理を操作する、以下のようなルートを想像してください。

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メソッドを使い、Fakeプロセスの最終的な終了コードを指定しています。最後に、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);

未指定プロセスの防止

もし、個別のテストや、テストスイート全体を通して、呼び出されたすべてのプロセスがFakeされていることを確認したい場合は、 preventStrayProcessesメソッドを呼び出してください。このメソッドを呼び出すと、対応するFake結果を持たないプロセスは、実際のプロセスを開始するのではなく、例外を投げます。

use Illuminate\Support\Facades\Process;

Process::preventStrayProcesses();

Process::fake([
    'ls *' => 'Test output...',
]);

// Fakeレスポンスが返る
Process::run('ls -la');

// 例外が投げられる
Process::run('bash import.sh');

ドキュメント章別ページ

ヘッダー項目移動

注目:アイコン:ページ内リンク設置(リンクがないヘッダーへの移動では、リンクがある以前のヘッダーのハッシュをURLへ付加します。

移動

クリックで即時移動します。

設定

適用ボタンクリック後に、全項目まとめて適用されます。

カラーテーマ
和文指定 Pagination
和文指定 Scaffold
Largeスクリーン表示幅
インデント
本文フォント
コードフォント
フォント適用確認

フォントの指定フィールドから、フォーカスが外れると、当ブロックの内容に反映されます。EnglishのDisplayもPreviewしてください。

フォント設定時、表示に不具合が出た場合、当サイトのクッキーを削除してください。

バックスラッシュを含むインライン\Code\Blockの例です。

以下はコードブロックの例です。

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * ユーザに関連する電話レコードを取得
     */
    public function phone()
    {
        return $this->hasOne('App\Phone');
    }
}

設定を保存する前に、表示が乱れないか必ず確認してください。CSSによるフォントファミリー指定の知識がない場合は、フォントを変更しないほうが良いでしょう。

キーボード・ショートカット

オープン操作

PDC

ページ(章)移動の左オフキャンバスオープン

HA

ヘッダー移動モーダルオープン

MS

移動/設定の右オフキャンバスオープン

ヘッダー移動

T

最初のヘッダーへ移動

E

最後のヘッダーへ移動

NJ

次ヘッダー(H2〜H4)へ移動

BK

前ヘッダー(H2〜H4)へ移動

その他

?

このヘルプページ表示
閉じる