프로세스
소개
라라벨은 Symfony Process 컴포넌트를 기반으로 표현력이 풍부하고 최소한의 API를 제공하여 라라벨 애플리케이션에서 외부 프로세스를 편리하게 호출할 수 있도록 합니다. 라라벨의 프로세스 기능은 가장 일반적인 사용 사례와 훌륭한 개발자 경험에 중점을 두고 있습니다.
프로세스 호출
프로세스를 호출하려면 Process 파사드에서 제공하는 run 및 start 메서드를 사용할 수 있습니다. run 메서드는 프로세스를 호출하고 프로세스 실행이 완료될 때까지 기다리는 반면, start 메서드는 비동기 프로세스 실행에 사용됩니다. 이 문서에서 두 가지 접근 방식을 모두 살펴보겠습니다. 먼저 기본 동기 프로세스를 호출하고 결과를 검사하는 방법을 살펴보겠습니다.
use Illuminate\Support\Facades\Process; $result = Process::run('ls -la'); return $result->output();
물론, run 메서드에서 반환된 Illuminate\Contracts\Process\ProcessResult 인스턴스는 프로세스 결과를 검사하는 데 사용할 수 있는 다양한 유용한 메서드를 제공합니다.
$result = Process::run('ls -la'); $result->successful();$result->failed();$result->exitCode();$result->output();$result->errorOutput();
예외 던지기
프로세스 결과가 있고 종료 코드가 0보다 큰 경우(실패를 나타냄) 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');
제한 시간
기본적으로 프로세스는 60초 이상 실행된 후 Illuminate\Process\Exceptions\ProcessTimedOutException 인스턴스를 던집니다. 그러나 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');
파이프라인
때로는 한 프로세스의 출력을 다른 프로세스의 입력으로 사용하고 싶을 수 있습니다. 이를 종종 프로세스의 출력을 다른 프로세스로 "파이핑"한다고 합니다. Process 파사드에서 제공하는 pipe 메서드를 사용하면 쉽게 수행할 수 있습니다. 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 및 시그널
id 메서드는 실행 중인 프로세스의 운영 체제 할당 프로세스 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();
프로세스가 완료될 때까지 기다리는 대신 프로세스 출력에 따라 대기를 중지하기 위해 waitUntil 메서드를 사용할 수 있습니다. Laravel은 waitUntil 메서드에 제공된 클로저가 true를 반환하면 프로세스 완료를 기다리는 것을 중지합니다.
$process = Process::start('bash import.sh'); $process->waitUntil(function (string $type, string $output) { return $output === 'Ready...';});
동시 프로세스
Laravel은 또한 동시적이고 비동기적인 프로세스 풀을 쉽게 관리할 수 있도록 하여 여러 작업을 동시에 쉽게 실행할 수 있습니다. 시작하려면 Illuminate\Process\Pool의 인스턴스를 받는 클로저를 수락하는 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();
풀 프로세스 이름 지정
숫자 키를 통해 프로세스 풀 결과에 접근하는 것은 표현력이 좋지 않습니다. 따라서 라라벨은 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);
테스팅
많은 라라벨 서비스는 테스트를 쉽고 표현력 있게 작성할 수 있도록 기능을 제공하며, 라라벨의 프로세스 서비스도 예외는 아닙니다. Process 파사드의 fake 메소드를 사용하면 프로세스가 호출될 때 스텁된/더미 결과를 반환하도록 라라벨에 지시할 수 있습니다.
프로세스 페이킹
라라벨의 프로세스 페이킹 기능을 살펴보기 위해 프로세스를 호출하는 라우트를 상상해 봅시다:
use Illuminate\Support\Facades\Process;use Illuminate\Support\Facades\Route; Route::get('/import', function () { Process::run('bash import.sh'); return 'Import complete!';});
이 경로를 테스트할 때, Laravel에게 Process 파사드에서 인자 없이 fake 메서드를 호출하여 모든 호출된 프로세스에 대해 가짜의 성공적인 프로세스 결과를 반환하도록 지시할 수 있습니다. 또한, 특정 프로세스가 "실행"되었는지도 단언할 수 있습니다.
<?php use Illuminate\Process\PendingProcess;use Illuminate\Contracts\Process\ProcessResult;use Illuminate\Support\Facades\Process; test('process is invoked', function () { Process::fake(); $response = $this->get('/import'); // 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; });});
<?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('/import'); // 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; }); }}
언급했듯이, Process 파사드에서 fake 메서드를 호출하면 Laravel에게 항상 출력 없이 성공적인 프로세스 결과를 반환하도록 지시합니다. 하지만, Process 파사드의 result 메서드를 사용하여 가짜 프로세스에 대한 출력과 종료 코드를 쉽게 지정할 수 있습니다:
Process::fake([ '*' => Process::result( output: 'Test output', errorOutput: 'Test error output', exitCode: 1, ),]);
특정 프로세스 페이킹하기
이전 예제에서 보셨듯이, Process 파사드는 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),]);
위의 예제를 자세히 살펴보겠습니다. 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);
assertRan 클로저에 전달되는 $process는 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);
Preventing Stray Processes
개별 테스트 또는 전체 테스트 스위트에서 호출된 모든 프로세스가 페이크 처리되었는지 확인하려면 preventStrayProcesses 메서드를 호출할 수 있습니다. 이 메서드를 호출한 후, 해당하는 페이크 결과가 없는 프로세스는 실제 프로세스를 시작하는 대신 예외를 발생시킵니다.
use Illuminate\Support\Facades\Process; Process::preventStrayProcesses(); Process::fake([ 'ls *' => 'Test output...',]); // 페이크 응답이 반환됩니다...Process::run('ls -la'); // 예외가 발생합니다...Process::run('bash import.sh');