本文へ移動
Laravel Tips

外部のコマンドを動かす(プロセス)

Laravel のアプリから ls や bash のような外部のコマンドを動かす Process の使い方を、結果の見方・オプション・並行実行・テスト用のにせものまで説明します。

アプリの中から、ls のような、パソコンのコマンドや、別のプログラムを動かしたいことがあります。この動いているコマンドやプログラムを、プロセスといいます。Laravel の Process は、Symfony Process というコンポーネント(部品)を、使いやすく包んだ機能です。よく使う場面に絞って、簡単に書けるようになっています。

プロセスを動かす#

プロセスを動かすには、Process ファサード(Process::run() のように、クラス名と :: で機能を呼べる窓口)の run と start を使います。run は、プロセスを動かして、終わるまで待ちます。start は、終わりを待たずに動かします(非同期といいます)。まず、終わるまで待つ、ふつうの動かし方と、結果の見方です。

php
use Illuminate\Support\Facades\Process;

$result = Process::run('ls -la');

return $result->output();

run が返す Illuminate\Contracts\Process\ProcessResult には、結果を調べるメソッドがそろっています。

php
$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 が返ります。

php
$result = Process::run('ls -la')->throw();

$result = Process::run('ls -la')->throwIf($condition);

プロセスのオプション#

動かす前に、プロセスの動き方を変えたいことがあります。作業フォルダ・時間切れ・環境変数などを変えられます。

メソッド 説明
path 作業フォルダを決める
input 標準入力(プロセスに渡す文字)を決める
timeout 時間切れまでの時間を決める
idleTimeout 出力がないまま続けてよい時間の上限を決める
forever 時間切れをなくす
env 環境変数を渡す・消す
tty TTY モードにする
quietly 出力を取っておかない

作業フォルダ#

path で、プロセスの作業フォルダ(コマンドを動かす場所)を決められます。呼ばなければ、いま動いている PHP のスクリプトの作業フォルダを引き継ぎます。

php
$result = Process::path(__DIR__)->run('ls -la');

入力#

input で、プロセスの「標準入力」(プロセスに文字を渡す入口)に、文字を渡せます。

php
$result = Process::input('Hello World')->run('cat');

時間切れ(タイムアウト)#

プロセスは、最初は、60 秒より長く動き続けると、Illuminate\Process\Exceptions\ProcessTimedOutException という例外を投げます。timeout で、この時間を変えられます。

php
$result = Process::timeout(120)->run('bash import.sh');

timeout と idleTimeout は、CarbonInterval(時間の長さを表すもの)も受け取れます。

php
use function Illuminate\Support\minutes;

$result = Process::timeout(minutes(2))->run('bash import.sh');

時間切れをなくしたいなら、forever を呼びます。

php
$result = Process::forever()->run('bash import.sh');

idleTimeout は、何も出力しないまま動き続けてよい、最大の秒数を決めます。

php
$result = Process::timeout(60)->idleTimeout(30)->run('bash import.sh');

環境変数#

env で、プロセスに環境変数(設定値)を渡せます。動かしたプロセスは、システムにある環境変数も、すべて引き継ぎます。

php
$result = Process::forever()
    ->env(['IMPORT_PATH' => __DIR__])
    ->run('bash import.sh');

引き継いだ環境変数を、プロセスから消したいときは、その環境変数の値を false にします。

php
$result = Process::forever()
    ->env(['LOAD_PATH' => false])
    ->run('bash import.sh');

TTY モード#

tty で、プロセスを TTY モード(端末のように動かすモード)にできます。TTY モードでは、プロセスの入力と出力が、自分のプログラムの入力と出力につながります。Vim や Nano のようなエディタ(文字を書き換えるプログラム)を、プロセスとして開けます。

php
Process::forever()->tty()->run('vim');

注意

TTY モードは、Windows では使えません。

プロセスの出力#

前に見たように、結果の output(標準出力)と errorOutput(エラー出力)で、出力を取り出せます。

php
use Illuminate\Support\Facades\Process;

$result = Process::run('ls -la');

echo $result->output();
echo $result->errorOutput();

出力を、動いているあいだに、順に受け取ることもできます。run の第2引数に、クロージャ(名前のない関数)を渡します。クロージャは、出力の「種類」(stdout か stderr)と、出力の文字を受け取ります。

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

seeInOutput と seeInErrorOutput で、ある文字が、出力に含まれているかを調べられます。

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

出力を取っておかない#

プロセスが、いらない出力を大量に出すなら、出力を取っておかないようにして、メモリを節約できます。プロセスを作るときに quietly を呼びます。

php
use Illuminate\Support\Facades\Process;

$result = Process::quietly()->run('bash import.sh');

パイプライン#

あるプロセスの出力を、別のプロセスの入力にしたいことがあります。これを「パイプでつなぐ」といいます。Process の pipe を使います。pipe は、つないだプロセスを順に動かし、いちばん最後のプロセスの結果を返します。

php
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 に渡せます。

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

出力を、動いているあいだに受け取りたいときは、pipe の第2引数に、クロージャを渡します。出力の「種類」(stdout か stderr)と、出力の文字を受け取ります。

php
$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 に渡した出力のクロージャにも渡されるので、出力がどのプロセスのものかが分かります。

php
$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 で、まだ動いているかを調べられます。

php
$process = Process::timeout(120)->start('bash import.sh');

while ($process->running()) {
    // ...
}

$result = $process->wait();

wait を呼ぶと、プロセスが終わるまで待って、ProcessResult を受け取れます。

php
$process = Process::timeout(120)->start('bash import.sh');

// ...

$result = $process->wait();

プロセス ID とシグナル#

id で、動いているプロセスに、OS(パソコンの基本のしくみ)が付けた、プロセス ID(番号)を取り出せます。

php
$process = Process::start('bash import.sh');

return $process->id();

signal で、動いているプロセスに「シグナル」(プロセスへの合図)を送れます。使えるシグナルの定数(決まった名前の値)の一覧は、PHP の公式ドキュメントにあります。

php
$process->signal(SIGUSR2);

非同期のプロセスの出力#

非同期のプロセスが動いているあいだ、output と errorOutput で、いままでの出力のすべてを取り出せます。前に取り出したあとに出た出力だけがほしいなら、latestOutput と latestErrorOutput を使います。

php
$process = Process::timeout(120)->start('bash import.sh');

while ($process->running()) {
    echo $process->latestOutput();
    echo $process->latestErrorOutput();

    sleep(1);
}

run と同じく、非同期のプロセスでも、start の第2引数にクロージャを渡して、動いているあいだに出力を受け取れます。

php
$process = Process::start('bash import.sh', function (string $type, string $output) {
    echo $output;
});

$result = $process->wait();

プロセスが終わるまで待つ代わりに、waitUntil で、出力の中身を見て、待つのをやめられます。waitUntil に渡したクロージャが true を返すと、Laravel は、プロセスの終わりを待つのをやめます。

php
$process = Process::start('bash import.sh');

$process->waitUntil(function (string $type, string $output) {
    return $output === 'Ready...';
});

非同期のプロセスの時間切れ#

非同期のプロセスが動いているあいだに、時間切れになっていないかを、ensureNotTimedOut で確かめられます。時間切れなら、前に説明した時間切れの例外を投げます。

php
$process = Process::timeout(120)->start('bash import.sh');

while ($process->running()) {
    $process->ensureNotTimedOut();

    // ...

    sleep(1);
}

同時に動かすプロセス#

たくさんのプロセスを、同時に(並行して)動かす「プール」も、簡単に作れます。pool を呼び、クロージャを渡します。このクロージャは、Illuminate\Process\Pool を受け取ります。

クロージャの中で、プールに入れるプロセスを決めます。プールを start で動かしたら、running で、動いているプロセスのコレクション(配列を便利に扱う入れ物)が取り出せます。

php
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 を、キーで取り出せます。

php
$results = $pool->wait();

echo $results[0]->output();

concurrently を使うと、非同期のプロセスのプールを動かして、すぐに結果を待てます。PHP の配列の分解([$a, $b] = ... の書き方)と組み合わせると、すっきり書けます。

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();

プールのプロセスに名前を付ける#

プールの結果を、数字のキーで取り出すのは、分かりにくいです。そこで、as で、プールの中のプロセスに、文字のキーを付けられます。このキーは、start に渡したクロージャにも渡されるので、出力がどのプロセスのものかが分かります。

php
$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 も簡単に取り出せます。

php
$processIds = $pool->running()->map->id();

プールの signal を呼ぶと、プールのすべてのプロセスに、シグナルを送れます。

php
$pool->signal(SIGUSR2);

テスト#

Laravel の多くの機能は、テスト(プログラムが正しく動くかを確かめるプログラム)を書きやすくする機能を持っています。プロセスも同じです。Process の fake を呼ぶと、プロセスを動かしたときに、本物の代わりに、にせものの結果を返させられます。

プロセスをにせものにする#

プロセスを動かすルートを考えます。

php
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
<?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
<?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 を使います。

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

特定のプロセスだけをにせものにする#

fake に配列を渡すと、プロセスごとに、別々のにせものの結果を決められます。

配列のキーに、にせものにしたいコマンドの形(パターン)を、値に、その結果を書きます。* は、ワイルドカード(何にでも合う印)として使えます。にせものにしていないコマンドは、本当に動かされます。にせものの結果は、Process の result で作れます。

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

終了コードやエラー出力を決めなくてよければ、にせものの結果を、ただの文字で書けます。

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

順番に結果を変える#

テストするコードが、同じコマンドを何度も動かすなら、動かすたびに、別のにせものの結果を返したいことがあります。Process の sequence を使います。

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

非同期のプロセスの動きをにせものにする#

ここまでは、run で動かす、終わりを待つプロセスのにせものを見ました。start で動かす非同期のプロセスを使うコードをテストするには、にせものの書き方を、もう少し細かくする必要があります。

非同期のプロセスを使う、次のルートを考えます。

php
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 を使います。

php
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#

そのプロセスが動かされたことを確かめます。

php
use Illuminate\Support\Facades\Process;

Process::assertRan('ls -la');

プロセスを、引数の配列で動かしたときは、同じ配列をアサーションに渡せます。

php
Process::assertRan(['php', 'artisan', 'migrate']);

assertRanTimes と assertDidntRun も、配列のコマンドを受け取れます。

assertRan は、クロージャも受け取ります。クロージャは、プロセスと、プロセスの結果を受け取るので、プロセスに設定したオプションを調べられます。クロージャが true を返すと、アサーションは「成功」します。

php
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#

そのプロセスが動かされていないことを確かめます。

php
use Illuminate\Support\Facades\Process;

Process::assertDidntRun('ls -la');

assertRan と同じく、クロージャも受け取ります。クロージャが true を返すと、アサーションは「失敗」します。

php
Process::assertDidntRun(fn (PendingProcess $process, ProcessResult $result) =>
    $process->command === 'ls -la'
);

assertRanTimes#

そのプロセスが、決めた回数だけ動かされたことを確かめます。

php
use Illuminate\Support\Facades\Process;

Process::assertRanTimes('ls -la', times: 3);

assertRanTimes も、クロージャを受け取ります。PendingProcess と ProcessResult を受け取るので、プロセスの設定を調べられます。クロージャが true を返し、プロセスが決めた回数だけ動かされていれば、アサーションは「成功」します。

php
Process::assertRanTimes(function (PendingProcess $process, ProcessResult $result) {
    return $process->command === 'ls -la';
}, times: 3);

assertRanInOrder#

プロセスが、決めた順に動かされたことを確かめます。

php
Process::assertRanInOrder([
    'git fetch',
    'composer install',
]);

assertRanInOrder は、ほかのプロセスのアサーションと同じく、コマンドの文字・コマンドの引数の配列・クロージャを受け取ります。

想定外のプロセスを禁止する#

1つのテストや、テスト全体で、動かしたすべてのプロセスが、にせものになっていることを確かめたいなら、preventStrayProcesses を呼びます。呼んだあとは、にせものの結果がないプロセスを動かそうとすると、本物のプロセスを始める代わりに、例外が投げられます。

php
use Illuminate\Support\Facades\Process;

Process::preventStrayProcesses();

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

// にせものの結果が返る
Process::run('ls -la');

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

関連するページ#

公式ドキュメント(英語)

2026年10月5日時点の内容をもとに、日本語でまとめています。

ページの一覧