1. Глубже в детали
  2. Процессы

Присоединяйся к нашему Telegram сообществу @webblend!

Здесь ты найдешь сниппеты по Laravel и полезные советы по веб-разработке.

Введение

Laravel предоставляет выразительный, минимальный API вокруг компонента Symfony Process, позволяя вам удобно вызывать внешние процессы из вашего Laravel-приложения. Возможности обработки 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');

Ввод

Вы можете предоставлять ввод через "стандартный ввод" процесса с помощью метода input:

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

Таймауты

По умолчанию процессы будут выбрасывать экземпляр 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');

Каналы

Иногда вам может потребоваться использовать вывод одного процесса в качестве ввода для другого процесса. Это часто называется «перенаправлением» вывода процесса в другой. Метод pipe, предоставленный фасадом Process, позволяет легко достичь этого. Метод pipe будет выполняться синхронно и возвращать результат процесса для последнего процесса в конвейере:

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()) {
// ...
}

Если вам не нужно настраивать отдельные процессы, составляющие конвейер, вы можете просто передать массив строк команд методу pipe:

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

Вывод процесса может быть собран в реальном времени, передав замыкание в качестве второго аргумента методу pipe. Замыкание будет получать два аргумента: «тип» вывода (stdout или stderr) и сам вывод:

$result = Process::pipe(function (Pipe $pipe) {
$pipe->command('cat example.txt');
$pipe->command('grep -i "laravel"');
}, function (string $type, string $output) {
echo $output;
});

Laravel также позволяет присваивать строковые ключи каждому процессу в конвейере с помощью метода as. Этот ключ также будет передан замыканию вывода, предоставленному методу pipe, что позволяет вам определить, к какому процессу относится вывод:

$result = Process::pipe(function (Pipe $pipe) {
$pipe->as('first')->command('cat example.txt');
$pipe->as('second')->command('grep -i "laravel"');
})->start(function (string $type, string $output, string $key) {
// ...
});

Асинхронные процессы

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

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

Метод 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 (string $type, string $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 (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();

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

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

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

И, для удобства, вы можете вызвать метод 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('/');
 
// Простая проверка процесса...
Process::assertRan('bash import.sh');
 
// Или проверка конфигурации процесса...
Process::assertRan(function (PendingProcess $process, ProcessResult $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, утверждение будет "пройдено":

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

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

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