프로세스
번역일: 2026년 6월 27일
프로세스
소개
Laravel은 Symfony Process 컴포넌트를 기반으로 간결하고 표현력 있는 API를 제공합니다. 이를 통해 Laravel 애플리케이션에서 외부 프로세스를 손쉽게 실행할 수 있습니다. 가장 일반적인 사용 사례에 초점을 맞추어 설계되었으며, 개발자 경험을 최우선으로 고려합니다.
프로세스 실행
프로세스를 실행하려면 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->command(); // 실행된 명령어
$result->successful(); // 성공 여부
$result->failed(); // 실패 여부
$result->output(); // 표준 출력(stdout)
$result->errorOutput(); // 에러 출력(stderr)
$result->exitCode(); // 종료 코드예외 던지기
종료 코드가 0보다 클 경우(실패를 의미) Illuminate\Process\Exceptions\ProcessFailedException 예외를 던지고 싶다면 throw 또는 throwIf 메서드를 사용할 수 있습니다. 프로세스가 성공하면 ProcessResult 인스턴스가 그대로 반환됩니다.
$result = Process::run('ls -la')->throw();
$result = Process::run('ls -la')->throwIf($condition);프로세스 옵션
프로세스를 실행하기 전에 작업 디렉터리, 타임아웃, 환경 변수 등 다양한 동작을 커스터마이징할 수 있습니다.
작업 디렉터리 경로
path 메서드로 프로세스의 작업 디렉터리를 지정할 수 있습니다. 지정하지 않으면 현재 PHP 스크립트의 작업 디렉터리를 상속합니다.
$result = Process::path(__DIR__)->run('ls -la');표준 입력(stdin)
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 메서드는 프로세스가 출력을 생성하지 않고 실행될 수 있는 최대 시간을 지정합니다. 아래 예시는 전체 타임아웃은 60초, 유휴 타임아웃은 30초로 설정합니다.
$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 모드를 활성화할 수 있습니다. Vim이나 Nano 같은 터미널 기반 편집기를 프로세스로 열 때 유용합니다.
Process::forever()->tty()->run('vim');WARNING
TTY 모드는 Windows에서 지원되지 않습니다.
프로세스 출력
앞서 설명했듯이, 프로세스 결과의 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;
});특정 문자열이 출력에 포함되어 있는지 확인하려면 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"',
]);두 번째 인자로 클로저를 전달하면 출력을 실시간으로 수집할 수 있습니다.
$result = Process::pipe(function (Pipe $pipe) {
$pipe->command('cat example.txt');
$pipe->command('grep -i "laravel"');
}, function (string $type, string $output) {
echo $output;
});as 메서드로 파이프라인 내 각 프로세스에 문자열 키를 지정하면, 출력 클로저에서 어떤 프로세스의 출력인지 구분할 수 있습니다.
$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로 출력을 생성한 프로세스를 구분
});비동기 프로세스
run이 프로세스를 동기적으로 실행하는 반면, start는 비동기적으로 실행합니다. 프로세스가 백그라운드에서 실행되는 동안 애플리케이션이 다른 작업을 계속 처리할 수 있습니다. running 메서드로 프로세스가 아직 실행 중인지 확인할 수 있습니다.
$process = Process::timeout(120)->start('bash import.sh');
while ($process->running()) {
// 프로세스 실행 중 다른 작업 처리 가능
}
$result = $process->wait();wait 메서드는 프로세스가 완료될 때까지 대기한 후 ProcessResult 인스턴스를 반환합니다.
$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);
}start 메서드의 두 번째 인자로 클로저를 전달하면 비동기 프로세스의 출력을 실시간으로 수집할 수도 있습니다.
$process = Process::start('bash import.sh', function (string $type, string $output) {
echo $output;
});
$result = $process->wait();프로세스가 완전히 종료될 때까지 기다리지 않고, 특정 출력이 나타날 때 대기를 중단하려면 waitUntil 메서드를 사용합니다. 클로저가 true를 반환하면 대기가 중단됩니다.
$process = Process::start('bash import.sh');
$process->waitUntil(function (string $type, string $output) {
return $output === 'Ready...';
});비동기 프로세스 타임아웃
비동기 프로세스 실행 중 ensureNotTimedOut 메서드를 호출하면 타임아웃 초과 여부를 확인할 수 있습니다. 타임아웃이 발생한 경우 타임아웃 예외가 던져집니다.
$process = Process::timeout(120)->start('bash import.sh');
while ($process->running()) {
$process->ensureNotTimedOut();
// ...
sleep(1);
}동시 프로세스
Laravel은 여러 비동기 프로세스를 풀(pool)로 묶어 동시에 실행하는 기능도 제공합니다. 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 메서드는 모든 풀 프로세스가 완료될 때까지 대기하고 결과를 반환합니다. 반환값은 배열처럼 접근 가능한 객체로, 키를 통해 각 프로세스의 ProcessResult 인스턴스에 접근할 수 있습니다.
$results = $pool->wait();
echo $results[0]->output();또는 concurrently 메서드를 사용하면 비동기 풀을 시작하고 결과를 즉시 기다릴 수 있습니다. PHP의 배열 구조 분해(destructuring)와 함께 사용하면 코드가 더욱 간결해집니다.
[$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) {
// $key로 어느 프로세스의 출력인지 구분
});
$results = $pool->wait();
return $results['first']->output();풀 프로세스 ID와 시그널
running 메서드는 풀 내 실행 중인 모든 프로세스의 컬렉션을 반환하므로, 각 프로세스의 ID를 쉽게 조회할 수 있습니다.
$processIds = $pool->running()->each->id();풀 내 모든 프로세스에 시그널을 한 번에 보내려면 풀의 signal 메서드를 사용합니다.
$pool->signal(SIGUSR2);테스트
Laravel의 프로세스 서비스도 다른 서비스들과 마찬가지로 테스트를 쉽게 작성할 수 있도록 지원합니다. Process 파사드의 fake 메서드를 사용하면 실제 프로세스 대신 가짜(stub) 결과를 반환하도록 지시할 수 있습니다.
프로세스 Fake 처리
프로세스를 실행하는 라우트를 예시로 살펴보겠습니다.
use Illuminate\Support\Facades\Process;
use Illuminate\Support\Facades\Route;
Route::get('/import', function () {
Process::run('bash import.sh');
return 'Import complete!';
});이 라우트를 테스트할 때 Process::fake()를 인자 없이 호출하면, 이후 실행되는 모든 프로세스는 성공한 가짜 결과를 반환합니다. 또한 특정 프로세스가 실행되었는지 어서션으로 확인할 수 있습니다.
Pest
<?php
use Illuminate\Contracts\Process\ProcessResult;
use Illuminate\Process\PendingProcess;
use Illuminate\Support\Facades\Process;
test('process is invoked', function () {
Process::fake();
$response = $this->get('/import');
// 단순 어서션
Process::assertRan('bash import.sh');
// 프로세스 설정값 검사
Process::assertRan(function (PendingProcess $process, ProcessResult $result) {
return $process->command === 'bash import.sh' &&
$process->timeout === 60;
});
});PHPUnit
<?php
namespace Tests\Feature;
use Illuminate\Contracts\Process\ProcessResult;
use Illuminate\Process\PendingProcess;
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');
// 단순 어서션
Process::assertRan('bash import.sh');
// 프로세스 설정값 검사
Process::assertRan(function (PendingProcess $process, ProcessResult $result) {
return $process->command === 'bash import.sh' &&
$process->timeout === 60;
});
}
}fake 메서드는 기본적으로 출력이 없는 성공 결과를 반환하지만, result 메서드를 사용하면 출력과 종료 코드를 직접 지정할 수 있습니다.
Process::fake([
'*' => Process::result(
output: '테스트 출력',
errorOutput: '테스트 에러 출력',
exitCode: 1,
),
]);특정 프로세스 Fake 처리
fake 메서드에 배열을 전달하면 명령어 패턴별로 서로 다른 가짜 결과를 지정할 수 있습니다. 배열의 키는 명령어 패턴이며, *를 와일드카드로 사용할 수 있습니다. Fake로 지정되지 않은 프로세스는 실제로 실행됩니다.
Process::fake([
'cat *' => Process::result(
output: '"cat" 명령어 테스트 출력',
),
'ls *' => Process::result(
output: '"ls" 명령어 테스트 출력',
),
]);종료 코드나 에러 출력을 별도로 지정할 필요가 없다면 문자열로 간단히 지정할 수도 있습니다.
Process::fake([
'cat *' => '"cat" 명령어 테스트 출력',
'ls *' => '"ls" 명령어 테스트 출력',
]);프로세스 시퀀스 Fake 처리
동일한 명령어가 여러 번 호출될 때 호출 순서에 따라 다른 결과를 반환하고 싶다면 sequence 메서드를 사용합니다.
Process::fake([
'ls *' => Process::sequence()
->push(Process::result('첫 번째 호출 결과'))
->push(Process::result('두 번째 호출 결과')),
]);비동기 프로세스 라이프사이클 Fake 처리
지금까지는 run으로 실행하는 동기 프로세스의 Fake 처리를 살펴봤습니다. start로 실행하는 비동기 프로세스를 테스트할 때는 describe 메서드로 더 상세한 라이프사이클을 정의할 수 있습니다.
예를 들어, 아래와 같이 비동기 프로세스를 사용하는 라우트가 있다고 가정합니다.
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';
});이 프로세스를 제대로 Fake 처리하려면 running이 true를 반환하는 횟수와 순차적으로 반환될 출력 내용을 지정해야 합니다. describe 메서드로 이를 구현할 수 있습니다.
Process::fake([
'bash import.sh' => Process::describe()
->output('표준 출력 첫 번째 줄')
->errorOutput('에러 출력 첫 번째 줄')
->output('표준 출력 두 번째 줄')
->exitCode(0)
->iterations(3),
]);output/errorOutput: 순서대로 반환될 출력 내용을 지정합니다.exitCode: 프로세스의 최종 종료 코드를 지정합니다.iterations:running메서드가true를 반환할 횟수를 지정합니다.
사용 가능한 어서션
Laravel은 기능 테스트에서 사용할 수 있는 다양한 프로세스 어서션을 제공합니다.
assertRan
특정 프로세스가 실행되었는지 검사합니다.
use Illuminate\Support\Facades\Process;
Process::assertRan('ls -la');클로저를 전달하면 프로세스의 설정값을 상세히 검사할 수 있습니다. 클로저가 true를 반환하면 어서션이 통과합니다. $process는 Illuminate\Process\PendingProcess 인스턴스이며, $result는 Illuminate\Contracts\Process\ProcessResult 인스턴스입니다.
Process::assertRan(fn ($process, $result) =>
$process->command === 'ls -la' &&
$process->path === __DIR__ &&
$process->timeout === 60
);assertDidntRun
특정 프로세스가 실행되지 않았는지 검사합니다.
use Illuminate\Support\Facades\Process;
Process::assertDidntRun('ls -la');클로저를 전달할 수도 있으며, 이 경우 클로저가 true를 반환하면 어서션이 실패합니다.
Process::assertDidntRun(fn (PendingProcess $process, ProcessResult $result) =>
$process->command === 'ls -la'
);assertRanTimes
특정 프로세스가 지정한 횟수만큼 실행되었는지 검사합니다.
use Illuminate\Support\Facades\Process;
Process::assertRanTimes('ls -la', times: 3);클로저를 전달하면 설정값도 함께 검사할 수 있습니다. 클로저가 true를 반환하고, 해당 횟수만큼 실행된 경우 어서션이 통과합니다.
Process::assertRanTimes(function (PendingProcess $process, ProcessResult $result) {
return $process->command === 'ls -la';
}, times: 3);예상치 못한 프로세스 방지
테스트 중 Fake 처리되지 않은 프로세스가 실수로 실행되는 것을 방지하고 싶다면 preventStrayProcesses 메서드를 호출합니다. 이 메서드를 호출한 이후, 대응하는 Fake 결과가 없는 프로세스는 실제로 실행되는 대신 예외를 발생시킵니다.
use Illuminate\Support\Facades\Process;
Process::preventStrayProcesses();
Process::fake([
'ls *' => '테스트 출력...',
]);
// Fake 결과 반환 (정상)
Process::run('ls -la');
// 예외 발생! (Fake 처리되지 않은 프로세스)
Process::run('bash import.sh');