Laravel 10: Что такое Processes / Процессы

Источник: «Processes»
Laravel представляет выразительный минимальный API для компонента Symfony Process, позволяющий удобно вызывать внешние процессы из приложения Laravel.

Введение

Laravel представляет выразительный минимальный API для компонента Symfony Process, позволяющий удобно вызывать внешние процессы из приложения Laravel. Возможности Process Laravel ориентированы на наиболее распространённые варианты использования и замечательные возможности для разработчиков.

Вызов Процессов

Для вызова процесса можно использовать методы run и start, предлагаемые фасадом 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, если код выхода больше нуля (что указывает на сбой), вы можете использовать методы throw и throwIf. Если процесс не завершился ошибкой, будет возвращён результат процесса:

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

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

Параметры Процесса

Конечно, вам может понадобиться настроить поведение процесса перед его вызовом. К счастью, Laravel позволяет настраивать различные функции процесса, такие как рабочий каталог, время ожидание и переменные окружения.

Путь к Рабочему Каталогу

Можно использовать метод path для указания рабочего каталога процесса. Если этот метод не вызывается, процесс унаследует рабочий каталог выполняемого в данный момент PHP скрипта:

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

Время Ожидания / Таймаут

По умолчанию процессы выбрасывают экземпляр 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 (stdout) и 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 также предлагает методы seeInOutput и seeInErrorOutput предоставляющие удобный способ определить, содержалась ли заданная строка в выводе процесса:

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

Идентификаторы Процессов и Сигналы

Метод pid может использоваться для получения назначенного операционной системой идентификатора запущенного процесса:

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

return $process->pid();

Можно использовать метод 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. Замыкание получит два аргумента: тип вывода (stdout или stderr) и саму строку вывода:

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

$result = $process->wait();

Параллельные/Конкурентные Процессы

Laravel упрощает управление пулом параллельных/конкурентных, асинхронных процессов, позволяя легко выполнять множество задач одновременно. Для начала вызовите метод 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 ($type, $output, $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 ($type, $output, $key) {
// ...
});

$results = $pool->wait();

return $results['first']->output();

Идентификаторы Процессов и Сигналы Пула

Поскольку метод пула процессов running предоставляет коллекцию всех запущенных процессов в пуле, вы можете легко получить доступ к идентификаторам процессов базового пула:

$processIds = $pool->running()->each->pid();

И для удобства вы можете вызвать метод signal для пула процессов, чтобы послать сигнал каждому процессу в пуле:

$pool->signal(SIGUSR2);

Тестирование

Многие службы Laravel предоставляют функциональные возможности помогающие легко и выразительно писать тесты, и служба процессов Laravel не является исключением. Метод fake фасады Process позволяет указать 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 без аргументов. Кроме того, мы можем даже утверждать, что данный процесс был запущен.

<?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 ($process, $result) {
return $process->command === 'bash import.sh' &&
$process->timeout === 60;
});
}
}

Как уже говорилось, вызов метода fake на фасаде Process будет указывать laravel всегда возвращать успешный результат процесса без вывода. Однако вы можете указать вывод и код завершения для фальсифицированных процессов используя метод result фасада Process:

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

Фальсификация Определённых Процессов

Как вы, возможно, заметили в предыдущем примере, фасад Process позволяет указывать различные фиктивные результаты для каждого процесса, передав массив методу fake.

Ключи массива должны представлять шаблоны команд, которые хотите подделать, и связанные с ними результаты. Симов * можно использовать как подстановочный знак. Любые команды процесса, которые не были фальсифицированы, будут фактически вызваны. Можете использовать метод result фасада Process для создания заглушек/фейковых результатов для этих команд:

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',
]);

Фальсификация Последовательности Процессов

Если тестируемый код вызывает несколько процессов с помощью одной и той же команды, вы можете захотеть назначить разные результаты фейкового процесса для каждого вызова процесса. Вы можете это сделать с помощью метода sequence фасада Process:

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. Кроме того, мы можем захотеть указать несколько строк вывода, которые должны возвращаться последовательно. Для это можно использовать метод describe фасада Process:

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 и errorOutput мы можем указать несколько строк, которые будут возвращены последовательно. Метод exitCode может использоваться для указания кода завершения фейкового процесса. Наконец метод iterations может использоваться для указания сколько раз метод running должен вернуть true.

Доступные Утверждения

Как ранее обсуждалось, Laravel предоставляет несколько утверждений процессов для ваших функциональных тестов. Ниже мы обсудим каждое из этих утверждений.

assertRan

Утверждение, что данный процесс был вызван:

use Illuminate\Support\Facades\Process;

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

Метод assertRan также принимает замыкание, которое получит экземпляр процесса и результат процесс, что позволит проверить настроенные параметры процесса. Если замыкание возвращает true, утверждение будет pass:

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 утверждение будет fail.

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

assertRanTimes

Утверждает, что данный процесс был вызван заданное количество раз:

use Illuminate\Support\Facades\Process;

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

Метод assertRanTimes также принимает замыкание получающее экземпляр процесса и результат процесса, что позволит проверить настроенные параметры процесса. Если замыкание возвращает true и процесс был вызван заданное количество раз, утверждение будет pass:

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

Предотвращение паразитных Процессов

Если вы хотите убедиться, что все запущенные процессы были фальсифицированы в отдельном тесте или во всём наборе тестов, то можете вызвать метод preventStrayProcesses. После вызова этого метода любые процессы, не имеющие соответствующего фейкового результата, выбросят исключение вместо запуска настоящего процесса:

    use Illuminate\Support\Facades\Process;

Process::preventStrayProcesses();

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

// Возвращает фейковый ответ...
Process::run('ls -la');

// Выбрасывает исключение...
Process::run('bash import.sh');

Дополнительные материалы

Предыдущая Статья

Laravel 10: Фасад Process

Следующая Статья

Laravel: Eloquent упорядочивание по hasMany отношениям