外部のコマンドを動かす(プロセス)
Laravel のアプリから ls や bash のような外部のコマンドを動かす Process の使い方を、結果の見方・オプション・並行実行・テスト用のにせものまで説明します。
アプリの中から、ls のような、パソコンのコマンドや、別のプログラムを動かしたいことがあります。この動いているコマンドやプログラムを、プロセスといいます。Laravel の Process は、Symfony Process というコンポーネント(部品)を、使いやすく包んだ機能です。よく使う場面に絞って、簡単に書けるようになっています。
プロセスを動かす#
プロセスを動かすには、Process ファサード(Process::run() のように、クラス名と :: で機能を呼べる窓口)の 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->command();
$result->successful();
$result->failed();
$result->output();
$result->errorOutput();
$result->exitCode();
| メソッド | 説明 |
|---|---|
command |
動かしたコマンドを返す |
successful |
成功したか |
failed |
失敗したか |
output |
標準出力(コマンドが普通に出した文字) |
errorOutput |
エラー出力(コマンドがエラーとして出した文字) |
exitCode |
終了コード(0 なら成功、それより大きければ失敗) |
失敗したら例外を投げる#
終了コードが 0 より大きい(失敗を表す)ときに、Illuminate\Process\Exceptions\ProcessFailedException という例外(エラーを知らせるしくみ)を投げたいときは、throw と throwIf を使います。失敗していなければ、ProcessResult が返ります。
$result = Process::run('ls -la')->throw();
$result = Process::run('ls -la')->throwIf($condition);
プロセスのオプション#
動かす前に、プロセスの動き方を変えたいことがあります。作業フォルダ・時間切れ・環境変数などを変えられます。
| メソッド | 説明 |
|---|---|
path |
作業フォルダを決める |
input |
標準入力(プロセスに渡す文字)を決める |
timeout |
時間切れまでの時間を決める |
idleTimeout |
出力がないまま続けてよい時間の上限を決める |
forever |
時間切れをなくす |
env |
環境変数を渡す・消す |
tty |
TTY モードにする |
quietly |
出力を取っておかない |
作業フォルダ#
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');
timeout と idleTimeout は、CarbonInterval(時間の長さを表すもの)も受け取れます。
use function Illuminate\Support\minutes;
$result = Process::timeout(minutes(2))->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');
注意
TTY モードは、Windows では使えません。
プロセスの出力#
前に見たように、結果の output(標準出力)と errorOutput(エラー出力)で、出力を取り出せます。
use Illuminate\Support\Facades\Process;
$result = Process::run('ls -la');
echo $result->output();
echo $result->errorOutput();
出力を、動いているあいだに、順に受け取ることもできます。run の第2引数に、クロージャ(名前のない関数)を渡します。クロージャは、出力の「種類」(stdout か stderr)と、出力の文字を受け取ります。
$result = Process::run('ls -la', function (string $type, string $output) {
echo $output;
});
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()) {
// ...
}
パイプラインの中のプロセスを、1つずつ細かく決めなくてよいなら、コマンドの文字の配列を pipe に渡せます。
$result = Process::pipe([
'cat example.txt',
'grep -i "laravel"',
]);
出力を、動いているあいだに受け取りたいときは、pipe の第2引数に、クロージャを渡します。出力の「種類」(stdout か stderr)と、出力の文字を受け取ります。
$result = Process::pipe(function (Pipe $pipe) {
$pipe->command('cat example.txt');
$pipe->command('grep -i "laravel"');
}, function (string $type, string $output) {
echo $output;
});
as で、パイプラインの中のプロセスに、文字の名前(キー)を付けられます。このキーは、pipe に渡した出力のクロージャにも渡されるので、出力がどのプロセスのものかが分かります。
$result = Process::pipe(function (Pipe $pipe) {
$pipe->as('first')->command('cat example.txt');
$pipe->as('second')->command('grep -i "laravel"');
}, 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 を呼ぶと、プロセスが終わるまで待って、ProcessResult を受け取れます。
$process = Process::timeout(120)->start('bash import.sh');
// ...
$result = $process->wait();
プロセス ID とシグナル#
id で、動いているプロセスに、OS(パソコンの基本のしくみ)が付けた、プロセス 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引数にクロージャを渡して、動いているあいだに出力を受け取れます。
$process = Process::start('bash import.sh', function (string $type, string $output) {
echo $output;
});
$result = $process->wait();
プロセスが終わるまで待つ代わりに、waitUntil で、出力の中身を見て、待つのをやめられます。waitUntil に渡したクロージャが true を返すと、Laravel は、プロセスの終わりを待つのをやめます。
$process = Process::start('bash import.sh');
$process->waitUntil(function (string $type, string $output) {
return $output === 'Ready...';
});
非同期のプロセスの時間切れ#
非同期のプロセスが動いているあいだに、時間切れになっていないかを、ensureNotTimedOut で確かめられます。時間切れなら、前に説明した時間切れの例外を投げます。
$process = Process::timeout(120)->start('bash import.sh');
while ($process->running()) {
$process->ensureNotTimedOut();
// ...
sleep(1);
}
同時に動かすプロセス#
たくさんのプロセスを、同時に(並行して)動かす「プール」も、簡単に作れます。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 は、配列のように使えるものを返します。プールの各プロセスの ProcessResult を、キーで取り出せます。
$results = $pool->wait();
echo $results[0]->output();
concurrently を使うと、非同期のプロセスのプールを動かして、すぐに結果を待てます。PHP の配列の分解([$a, $b] = ... の書き方)と組み合わせると、すっきり書けます。
[$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();
プールのプロセスに名前を付ける#
プールの結果を、数字のキーで取り出すのは、分かりにくいです。そこで、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()->map->id();
プールの signal を呼ぶと、プールのすべてのプロセスに、シグナルを送れます。
$pool->signal(SIGUSR2);
テスト#
Laravel の多くの機能は、テスト(プログラムが正しく動くかを確かめるプログラム)を書きやすくする機能を持っています。プロセスも同じです。Process の 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 を引数なしで呼びます。すると、動かしたすべてのプロセスが、成功したにせものの結果を返すようになります。さらに、そのプロセスが「動かされた」ことを、アサーション(「こうなっているはず」を確かめる命令)で確かめられます。
Pest で書く場合です。
<?php
use Illuminate\Contracts\Process\ProcessResult;
use Illuminate\Process\PendingProcess;
use Illuminate\Support\Facades\Process;
test('process is invoked', function () {
Process::fake();
$response = $this->get('/import');
// かんたんなプロセスのアサーション
Process::assertRan('bash import.sh');
// プロセスの設定を調べるアサーション
Process::assertRan(function (PendingProcess $process, ProcessResult $result) {
return $process->command === 'bash import.sh' &&
$process->timeout === 60;
});
});
PHPUnit で書く場合です。
<?php
namespace Tests\Feature;
use Illuminate\Contracts\Process\ProcessResult;
use Illuminate\Process\PendingProcess;
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');
// かんたんなプロセスのアサーション
Process::assertRan('bash import.sh');
// プロセスの設定を調べるアサーション
Process::assertRan(function (PendingProcess $process, ProcessResult $result) {
return $process->command === 'bash import.sh' &&
$process->timeout === 60;
});
}
}
fake を呼ぶと、Laravel は、出力がなく、成功した結果を、いつも返します。にせもののプロセスの出力と終了コードを決めたいときは、Process の result を使います。
Process::fake([
'*' => Process::result(
output: 'Test output',
errorOutput: 'Test error output',
exitCode: 1,
),
]);
特定のプロセスだけをにせものにする#
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 |
にせものの標準出力を1行足す(呼んだ順に返る) |
errorOutput |
にせもののエラー出力を1行足す(呼んだ順に返る) |
exitCode |
にせもののプロセスの、最後の終了コードを決める |
iterations |
running が true を返す回数を決める |
使えるアサーション#
前に説明したとおり、Laravel には、機能のテストで使える、プロセスのアサーションがいくつかあります。1つずつ見ます。
| アサーション | 説明 |
|---|---|
assertRan |
そのプロセスが動かされたことを確かめる |
assertDidntRun |
そのプロセスが動かされていないことを確かめる |
assertRanTimes |
そのプロセスが、決めた回数だけ動かされたことを確かめる |
assertRanInOrder |
プロセスが、決めた順に動かされたことを確かめる |
assertRan#
そのプロセスが動かされたことを確かめます。
use Illuminate\Support\Facades\Process;
Process::assertRan('ls -la');
プロセスを、引数の配列で動かしたときは、同じ配列をアサーションに渡せます。
Process::assertRan(['php', 'artisan', 'migrate']);
assertRanTimes と assertDidntRun も、配列のコマンドを受け取れます。
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 と同じく、クロージャも受け取ります。クロージャが 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 も、クロージャを受け取ります。PendingProcess と ProcessResult を受け取るので、プロセスの設定を調べられます。クロージャが true を返し、プロセスが決めた回数だけ動かされていれば、アサーションは「成功」します。
Process::assertRanTimes(function (PendingProcess $process, ProcessResult $result) {
return $process->command === 'ls -la';
}, times: 3);
assertRanInOrder#
プロセスが、決めた順に動かされたことを確かめます。
Process::assertRanInOrder([
'git fetch',
'composer install',
]);
assertRanInOrder は、ほかのプロセスのアサーションと同じく、コマンドの文字・コマンドの引数の配列・クロージャを受け取ります。
想定外のプロセスを禁止する#
1つのテストや、テスト全体で、動かしたすべてのプロセスが、にせものになっていることを確かめたいなら、preventStrayProcesses を呼びます。呼んだあとは、にせものの結果がないプロセスを動かそうとすると、本物のプロセスを始める代わりに、例外が投げられます。
use Illuminate\Support\Facades\Process;
Process::preventStrayProcesses();
Process::fake([
'ls *' => 'Test output...',
]);
// にせものの結果が返る
Process::run('ls -la');
// 例外が投げられる
Process::run('bash import.sh');
関連するページ#
公式ドキュメント(英語)
2026年10月5日時点の内容をもとに、日本語でまとめています。