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

# 进程

> 介绍如何使用 Laravel 的 Process Facade 执行外部 shell 命令，并简洁地处理同步、异步与并发调用。

## 简介

Laravel 的 Process Facade 是对 [Symfony Process 组件](https://symfony.com/doc/current/components/process.html)的一层轻量封装。
它为在 Laravel 应用中调用 shell 命令、处理执行结果提供了简洁的 API。

```mermaid theme={null}
flowchart LR
    A["Laravel 应用"] --> B["Process<br>Facade"]
    B --> C["shell 命令"]
    C --> D["ProcessResult<br>(output / exitCode)"]
    D --> A
```

## 启动进程

`Process::run()` 用于同步执行命令并获取结果。

```php theme={null}
use Illuminate\Support\Facades\Process;

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

return $result->output();
```

`ProcessResult` 实例提供了一系列检查结果的方法。

```php theme={null}
$result = Process::run('ls -la');

$result->command();       // 执行的命令
$result->successful();    // 是否成功（exit code 为 0）
$result->failed();        // 是否失败（exit code 非 0）
$result->output();        // 标准输出（stdout）
$result->errorOutput();   // 标准错误输出（stderr）
$result->exitCode();      // 退出码
```

### 失败时抛出异常

要在退出码大于 0 时抛出 `Illuminate\Process\Exceptions\ProcessFailedException`，可以使用 `throw()` 或 `throwIf()`。

```php theme={null}
$result = Process::run('ls -la')->throw();

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

## 进程选项

### 工作目录

用 `path()` 指定工作目录，省略时会使用当前 PHP 脚本的工作目录。

```php theme={null}
$result = Process::path(__DIR__)->run('ls -la');
```

### 标准输入

`input()` 可以将数据写入进程的标准输入。

```php theme={null}
$result = Process::input('Hello World')->run('cat');
```

### 超时

默认超时是 60 秒，可以通过 `timeout()` 修改。超时后进程会被中断并抛出 `ProcessTimedOutException`。

```php theme={null}
$result = Process::timeout(120)->run('bash import.sh');
```

也可以使用 `CarbonInterval` 相关辅助函数。

```php theme={null}
use function Illuminate\Support\minutes;

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

若要关闭超时，可以使用 `forever()`。

```php theme={null}
$result = Process::forever()->run('bash import.sh');
```

还可以设置空闲超时（一段时间没有输出即视为超时）。

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

### 环境变量

`env()` 可以新增或覆盖环境变量。系统环境变量会自动继承。

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

若要排除继承的变量，可以将其设为 `false`。

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

### 关闭输出

如果不需要大量输出，可以用 `quietly()` 节省内存。

```php theme={null}
$result = Process::quietly()->run('bash import.sh');
```

### 实时输出

给 `run()` 的第二个参数传入闭包，即可实时接收输出。

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

## 管道

使用 `Process::pipe()` 可以把一条命令的输出作为下一条命令的输入。

```php theme={null}
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()) {
    // ...
}
```

也可以传入命令字符串数组。

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

给每个进程用 `as()` 打上 key，就能在输出闭包中判断输出来自哪个进程。

```php theme={null}
$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) {
    // $key 为 'first' 或 'second'
});
```

## 异步进程

`Process::start()` 可以异步启动进程。进程运行期间，应用可以继续处理其他工作。

```php theme={null}
$process = Process::timeout(120)->start('bash import.sh');

while ($process->running()) {
    // 其他处理
}

$result = $process->wait();
```

### 进程 ID 与信号

`id()` 用来获取运行中进程的 OS 进程 ID。

```php theme={null}
$process = Process::start('bash import.sh');

return $process->id();
```

`signal()` 可以向进程发送信号。

```php theme={null}
$process->signal(SIGUSR2);
```

### 异步进程的输出

运行过程中通过 `latestOutput()` 与 `latestErrorOutput()` 可以获取自上次取用以来的增量输出。

```php theme={null}
$process = Process::timeout(120)->start('bash import.sh');

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

    sleep(1);
}
```

要等某段输出出现再继续，可以使用 `waitUntil()`。

```php theme={null}
$process = Process::start('bash import.sh');

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

### 异步进程的超时检查

在循环中调用 `ensureNotTimedOut()`，如果已经超时就会抛出异常。

```php theme={null}
$process = Process::timeout(120)->start('bash import.sh');

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

    sleep(1);
}
```

## 并发进程

使用 `Process::pool()` 可以并发执行多个进程。

```php theme={null}
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();
```

使用 `concurrently()` 可以一行完成“启动池并等待结果”。

```php theme={null}
[$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()` 给每个进程加上字符串 key，可以更清晰地取得结果。

```php theme={null}
$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();
```

也可以向整个池发送信号。

```php theme={null}
$pool->signal(SIGUSR2);
```

## 测试

### 伪造进程

`Process::fake()` 可以在不真正执行 shell 命令的情况下测试。

```php theme={null}
use Illuminate\Support\Facades\Process;

Process::fake();

$response = $this->get('/import');

Process::assertRan('bash import.sh');
```

也可以指定输出和退出码。

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

### 伪造特定命令

可以以通配符或命令字符串作为 key 分别配置。

```php theme={null}
Process::fake([
    'cat *'    => Process::result(output: 'file contents'),
    'ls -la'   => Process::result(output: 'file listing'),
]);
```

### 序列化的假响应

同一条命令被多次调用时，可以按顺序指定返回结果。

```php theme={null}
Process::fake([
    'ls *' => [
        Process::result('first time'),
        Process::result('second time'),
    ],
]);
```

### 断言

| 方法                                      | 说明           |
| --------------------------------------- | ------------ |
| `Process::assertRan('command')`         | 断言命令被执行      |
| `Process::assertNotRan('command')`      | 断言命令未被执行     |
| `Process::assertRan(fn)`                | 通过闭包做更细致的校验  |
| `Process::assertRanTimes('command', 3)` | 断言命令被执行了指定次数 |

### 阻止意外进程

调用 `Process::preventStrayProcesses()` 后，如果执行了未 fake 的命令，就会抛出异常。

```php theme={null}
Process::preventStrayProcesses();

Process::fake([
    'ls *' => Process::result('file listing'),
]);

// 异常：'bash import.sh' 没有被 fake
Process::run('bash import.sh');
```

## 相关页面

<Columns cols={2}>
  <Card title="Artisan 控制台" icon="terminal" href="/zh/artisan">
    创建自定义命令并从中调用进程
  </Card>

  <Card title="队列" icon="list" href="/zh/queues">
    与异步处理的对比
  </Card>
</Columns>


## Related topics

- [WebSocket (Jetstream / Firehose)](/zh/packages/laravel-bluesky/websocket.md)
- [Labeler](/zh/packages/laravel-bluesky/labeler.md)
- [Laravel Reverb](/zh/reverb.md)
- [缓存](/zh/cache.md)
- [TCP 模式](/zh/packages/laravel-copilot-sdk/tcp-mode.md)
