> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# パッケージの外部プロセス実行とProcess::fake

> Laravel 13のProcess facadeを使い、外部コマンドの引数、タイムアウト、失敗例外、テストでのfakeと配列コマンドの照合を解説します。

パッケージが `ffmpeg` や `git` などの外部コマンドを呼ぶと、利用アプリの環境差や失敗がそのままパッケージの挙動になります。`shell_exec()` や `exec()` を直接呼ぶのではなく、**Process facade** で実行し、テストでは `Process::fake()` で置き換えます。

> [!NOTE]
>
> このページはLaravel 13を対象に、Framework `v13.42.0` の実装を確認しています。[プロセス](https://kawax.biz/jp/processes.md)と[パッケージのテスト](https://kawax.biz/jp/advanced/package-testing.md)を前提とします。ここで扱う配列コマンドの照合は、Process内部で使われるSymfony Processの引数変換を前提にしています。

## 直接実行を避ける理由

直接の関数呼び出しは、テストで差し替えられず、タイムアウトや終了コードの扱いも呼び出し側ごとにばらつきます。Process facadeを挟むと、実行の記録、fake、タイムアウト、失敗時の例外を統一できます。

```mermaid theme={null}
flowchart LR
    A["パッケージの<br>ラッパークラス"] --> B["Process facade<br>PendingProcess"]
    B --> C{"fakeは<br>登録済み?"}
    C -->|はい| D["FakeProcessResult<br>外部コマンドは起動しない"]
    C -->|いいえ| E["外部コマンドを起動"]
    E --> F["ProcessResult"]
    D --> F
    F --> G["throw()で失敗を例外化"]
```

## 配列でコマンドを渡す

文字列のコマンドは `sh` の解釈を通るため、利用者の入力を含めると引用符の問題や注入の余地が生まれます。パッケージの内部では配列で引数を渡します。

```php theme={null}
<?php

namespace Vendor\Package\Support;

use Illuminate\Support\Facades\Process;

final class VideoConverter
{
    public function toMp4(string $source, string $target): void
    {
        Process::timeout(120)
            ->run(['ffmpeg', '-y', '-i', $source, $target])
            ->throw();
    }
}
```

`PendingProcess` の既定のタイムアウトは `60` 秒、アイドルタイムアウトは `null` です。長時間の変換では `timeout()` で明示し、出力が止まる種類の処理では `idleTimeout()` を併用します。

## 失敗時の例外とメッセージ

`ProcessResult::throw()` は終了コードが成功でない場合に `ProcessFailedException` を投げます。メッセージには実行したコマンド、終了コード、標準出力と標準エラー出力が含まれます。

> [!WARNING]
>
> コマンドの引数に秘密情報を入れると、`ProcessFailedException` のメッセージに含まれて、ログや画面に出る可能性があります。トークンなどは引数にせず、`env()` で渡すか、コマンドの設定ファイルに書く方式を選びます。

タイムアウトは `ProcessTimedOutException`、アイドルタイムアウトは `ProcessIdleTimedOutException` で表されます。パッケージの利用者には、どの例外を投げるかと、捕まえたときに何を期待できるかを明記します。

## テストでfakeする

テストでは `Process::fake()` で外部コマンドを置き換えます。`Process::result()` は標準出力、標準エラー、終了コードを指定できます。

```php theme={null}
<?php

namespace Vendor\Package\Tests\Feature;

use Illuminate\Contracts\Process\ProcessResult;
use Illuminate\Process\Exceptions\ProcessFailedException;
use Illuminate\Process\PendingProcess;
use Illuminate\Support\Facades\Process;
use Vendor\Package\Support\VideoConverter;
use Vendor\Package\Tests\TestCase;

class VideoConverterTest extends TestCase
{
    public function test_it_runs_ffmpeg_with_the_expected_arguments(): void
    {
        Process::fake([
            '*' => Process::result(output: 'converted'),
        ]);

        (new VideoConverter)->toMp4('/tmp/in.mov', '/tmp/out.mp4');

        Process::assertRan(
            fn (PendingProcess $process, ProcessResult $result) => $process->command === ['ffmpeg', '-y', '-i', '/tmp/in.mov', '/tmp/out.mp4']
        );
    }

    public function test_it_fails_when_ffmpeg_exits_with_an_error(): void
    {
        Process::fake([
            '*' => Process::result(errorOutput: 'Invalid data', exitCode: 1),
        ]);

        $this->expectException(ProcessFailedException::class);

        (new VideoConverter)->toMp4('/tmp/in.mov', '/tmp/out.mp4');
    }
}
```

`assertRan()` にクロージャを渡すと、`PendingProcess` の `command`、`timeout`、`environment` などを確認できます。配列コマンドは `$process->command` と完全一致で比較されるため、引数の順序や値の変更はテストで検出されます。

## 配列コマンドのfakeキー

Process内部では、`fake()` のキーを `getCommandline()` の結果と `Str::is()` で照合します。配列コマンドはシェル向けに引用符付きの文字列へ変換されるため、`'ffmpeg*'` のような文字列のパターンは一致しません。配列コマンドのテストでは次のいずれかを使います。

| 方法 | 使いどころ | 注意点 |
| - | - | - |
| `'*'` のキー | コマンドに関係なく同じ結果を返す | 別のコマンドが呼ばれても気づきにくい |
| `Process::assertRan()` | 呼び出し後に引数を検証する | fakeの応答は別に用意する |
| クロージャのfake | 引数に応じて結果を変える | 処理が複雑になるため、テストの目的を絞る |

## 予期しない外部コマンドを防ぐ

テストで実行されるべきでない外部コマンドを検出するには、`preventStrayProcesses()` を使います。fakeに一致しないコマンドは `RuntimeException` になり、実際には起動しません。

```php theme={null}
protected function setUp(): void
{
    parent::setUp();

    Process::preventStrayProcesses();
}
```

## 注入されたFactoryを使う場合

パッケージのコンストラクタで `Illuminate\Process\Factory` を受け取る設計では、`Process::fake()` の状態が注入先まで届くかを確認します。Facadeは解決済みインスタンスをキャッシュし、`swap()` でコンテナのインスタンスを置き換えます。一方、`Factory` がコンテナで共有されるかは、フレームワークの明示的な登録に頼れないため、テストで実際に確認します。

注入先まで届かない場合は、テストでFactoryを明示的に差し替えるか、`Process` facadeを経由する設計にします。どちらの場合も、`assertRan()` で呼び出しが記録されたことを確認すると、差し替え漏れを検出できます。

## 長期保守で追加する検証

| 検証 | 目的 |
| - | - |
| 配列の引数順序を固定したテスト | `-y` や入出力の位置が変わったときに気づく |
| 失敗時の例外とメッセージの確認 | 利用者のエラー処理が変わらないことを保つ |
| `timeout()` と `idleTimeout()` の既定値の確認 | 既定値の変更を利用者の挙動変化として扱う |
| `preventStrayProcesses()` の有効化 | 想定外の外部コマンド起動をテストで止める |
| 秘密情報を引数に入れていないことの確認 | 例外メッセージやログへの漏えいを防ぐ |

## 参照した一次情報

* [Laravel公式ドキュメント: プロセス](https://github.com/laravel/docs/blob/13.x/processes.md)
* [Laravel公式ドキュメント: Process::fake と assertRan](https://github.com/laravel/docs/blob/13.x/processes.md#faking-processes)
* [Process Factory: fakeとassertRan](https://github.com/laravel/framework/blob/v13.42.0/src/Illuminate/Process/Factory.php)
* [PendingProcess: 既定のタイムアウトとfakeの照合](https://github.com/laravel/framework/blob/v13.42.0/src/Illuminate/Process/PendingProcess.php)
* [ProcessResult: throw()の挙動](https://github.com/laravel/framework/blob/v13.42.0/src/Illuminate/Process/ProcessResult.php)
* [ProcessFailedException: メッセージの構成](https://github.com/laravel/framework/blob/v13.42.0/src/Illuminate/Process/Exceptions/ProcessFailedException.php)
* [Facade: キャッシュとswap()](https://github.com/laravel/framework/blob/v13.42.0/src/Illuminate/Support/Facades/Facade.php)


## Related topics

- [応用トピック](https://kawax.biz/jp/advanced/index.md)
- [パッケージのキャッシュとoptimizeへの統合](https://kawax.biz/jp/advanced/package-optimization.md)
- [パッケージの診断情報をaboutに追加する](https://kawax.biz/jp/advanced/package-about.md)
- [パッケージの定期タスク登録と保守](https://kawax.biz/jp/advanced/package-scheduling.md)
- [パッケージのルート登録とキャッシュ](https://kawax.biz/jp/advanced/package-routes.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.