Processes

Introduction

Laravel 提供了一个富有表现力的、最小化的 APISymfony 进程组件,允许您从 Laravel 应用程序方便地调用外部进程。 Laravel 的流程功能专注于最常见的用例和出色的开发人员体验。

调用进程

要调用流程,您可以使用runstart 提供的方法Process 正面。这run 方法将调用一个进程并等待进程完成执行,而start 方法用于异步流程执行。我们将在本文档中检查这两种方法。首先,让我们检查如何调用基本的同步流程并检查其结果:

use Illuminate\Support\Facades\Process;

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

return $result->output();

当然,Illuminate\Contracts\Process\ProcessResult 返回的实例run 方法提供了多种有用的方法,可用于检查处理结果:

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

$result->successful();
$result->failed();
$result->exitCode();
$result->output();
$result->errorOutput();

抛出异常

如果你有一个过程结果并想抛出一个实例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

您可以使用input 方法:

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

Timeouts

默认情况下,进程将抛出一个实例Illuminate\Process\Exceptions\ProcessTimedOutException 执行超过 60 秒后。但是,您可以通过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 (标准输出)和errorOutput (stderr) 处理结果的方法:

use Illuminate\Support\Facades\Process;

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

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

但是,也可以通过将闭包作为第二个参数传递给run 方法。闭包将接收两个参数:输出的“类型”(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');

异步进程

虽然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);

异步过程输出

当异步进程运行时,您可以使用outputerrorOutput 方法;但是,您可以利用latestOutputlatestErrorOutput 访问自上次检索输出以来发生的进程的输出:

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

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

    sleep(1);
}

run 方法,也可以通过将闭包作为第二个参数传递给start 方法。闭包将接收两个参数:输出的“类型”(stdout 或者stderr) 和输出字符串本身:

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

$result = $process->wait();

并发进程

Laravel 还使管理并发异步进程池变得轻而易举,让您可以轻松地同时执行许多任务。首先,调用pool 方法,它接受一个接收实例的闭包Illuminate\Process\Pool.

在这个闭包中,您可以定义属于池的进程。一旦进程池通过start 方法,您可以访问collection 通过运行进程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);

Testing

许多 Laravel 服务提供的功能可以帮助您轻松而富有表现力地编写测试,Laravel 的流程服务也不例外。这Process 门面的fake 方法允许您指示 Laravel 在调用进程时返回存根/虚拟结果。

伪造进程

为了探索 Laravel 伪造进程的能力,让我们想象一个调用进程的路由:

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

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

    return 'Import complete!';
});

当测试这个路由时,我们可以指示 Laravel 通过调用fake 上的方法Process 没有参数的外观。此外,我们甚至可以assert 一个给定的过程是“运行”的:

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

        // Simple process assertion...
        Process::assertRan('bash import.sh');

        // Or, inspecting the process configuration...
        Process::assertRan(function (PendingProcess $process, ProcessResult $result) {
            return $process->command === 'bash import.sh' &&
                   $process->timeout === 60;
        });
    }
}

如前所述,调用fake 上的方法Process facade 将指示 Laravel 始终返回一个成功的处理结果,但没有输出。但是,您可以使用Process 门面的result 方法:

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

伪造特定进程

正如您在前面的示例中可能已经注意到的那样,Process facade 允许您通过将数组传递给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),
]);

让我们深入研究上面的例子。使用outputerrorOutput 方法,我们可以指定将按顺序返回的多行输出。这exitCode 方法可用于指定假进程的最终退出代码。最后,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
);

$process 传给了assertRan 闭包是一个实例Illuminate\Process\PendingProcess, 而$result 是一个实例Illuminate\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);

防止杂散过程

如果你想确保在你的单个测试或完整的测试套件中所有被调用的进程都是伪造的,你可以调用preventStrayProcesses 方法。调用此方法后,任何没有相应假结果的进程都会抛出异常,而不是启动实际进程:

use Illuminate\Support\Facades\Process;

Process::preventStrayProcesses();

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

// Fake response is returned...
Process::run('ls -la');

// An exception is thrown...
Process::run('bash import.sh');
豫ICP备18041297号-2