symfony/process
Symfony Process executes system commands in isolated subprocesses with robust control over input/output, environment variables, timeouts, signals, and errors. Ideal for running CLI tools safely, streaming output, and integrating background tasks in PHP apps.
Installation:
composer require symfony/process
No additional configuration is required—just autoload via Composer.
First Use Case: Execute a simple command and capture output:
use Symfony\Component\Process\Process;
use Symfony\Component\Process\Exception\ProcessFailedException;
$process = new Process(['ls', '-la']);
$process->run();
if (!$process->isSuccessful()) {
throw new ProcessFailedException($process);
}
echo $process->getOutput();
Where to Look First:
Process class methods: run(), getOutput(), isSuccessful(), getErrorOutput().setEnv(), getEnv().$process = new Process(['php', 'artisan', 'migrate']);
$process->run();
// Output handling
echo $process->getOutput(); // stdout
echo $process->getErrorOutput(); // stderr
$process = new Process(['./script.sh']);
$process->setEnv([
'DB_HOST' => 'localhost',
'APP_ENV' => 'production',
'ARRAY_VAR' => ['val1', 'val2'], // Supported in v8.1.0+
]);
$process->run();
$process = new Process(['tail', '-f', 'logfile.log']);
$process->start();
while ($process->isRunning()) {
echo $process->getOutput();
usleep(100); // Throttle to avoid CPU overload
}
$process = new Process(['php', 'slow-script.php']);
$process->setTimeout(30); // 30 seconds
$process->run();
if ($process->isTimedOut()) {
$process->stop();
}
use Symfony\Component\Process\Process;
$command = 'php artisan queue:work --once';
$process = new Process(explode(' ', $command));
// Run in background (detached)
$process->start();
// config/app.php
'bindings' => [
Symfony\Component\Process\Process::class => function ($app) {
return new Process(['php', 'artisan']);
},
];
use Symfony\Component\Process\Process;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
class RunProcessJob implements ShouldQueue
{
use Queueable;
public function handle()
{
$process = new Process(['php', 'artisan', 'notify:users']);
$process->run();
$this->logProcessOutput($process);
}
}
// config/process.php
return [
'default_env' => [
'APP_ENV' => env('APP_ENV'),
'APP_DEBUG' => env('APP_DEBUG', false),
],
'commands' => [
'migrate' => ['php', 'artisan', 'migrate'],
'queue' => ['php', 'artisan', 'queue:work', '--once'],
],
];
use Symfony\Component\Process\Process;
use Symfony\Component\Process\Exception\ProcessFailedException;
public function testProcessExecution()
{
$process = new Process(['php', 'artisan', 'list']);
$process->mustRun();
$this->assertStringContainsString('migrate', $process->getOutput());
}
$process = new Process(['bash']);
$process->setPty(true); // Enable interactive shell
$process->start();
$process->write('ls -la');
echo $process->getOutput();
$process = new Process(['sleep', '10']);
$process->start();
if ($process->isRunning()) {
$process->signal(SIGTERM); // Graceful shutdown
}
$command = Process::escapeArgumentList([
'php',
'artisan',
'queue:work',
'--once',
]);
$processes = [];
for ($i = 0; $i < 5; $i++) {
$process = new Process(['php', 'artisan', 'queue:work', '--once']);
$process->start();
$processes[] = $process;
}
foreach ($processes as $p) {
$p->wait();
}
Environment Variable Limits (Windows)
setEnv() sparingly or split into multiple processes.if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
$process->setEnv(['KEY' => 'value']); // Avoid arrays
}
Broken Pipes (Stdin/Stdout)
proc_open may fail silently on broken pipes (e.g., closed terminal).mustRun() or check isSuccessful():
$process->mustRun(); // Throws \RuntimeException on failure
Array Environment Variables (Pre-v8.1.0)
['KEY' => ['val1', 'val2']].^8.1.0 or flatten arrays manually:
$process->setEnv(['KEY' => json_encode(['val1', 'val2'])]);
PTY Mode on Windows
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
$process->setPty(false);
}
Signal Handling Race Conditions
SIGTERM may not propagate immediately.isRunning():
$process->signal(SIGTERM);
usleep(500000); // 0.5s delay
Log Raw Output
$process = new Process(['ls', '/nonexistent']);
$process->run();
file_put_contents(
storage_path('logs/process_debug.log'),
$process->getOutput() . "\n" . $process->getErrorOutput()
);
Check Process Status
if ($process->isRunning()) {
echo "Process is still running...\n";
} elseif ($process->isSuccessful()) {
echo "Success!\n";
} else {
echo "Failed with exit code: " . $process->getExitCode() . "\n";
}
Enable Verbose Mode
$process = new Process(['php', '-v', 'artisan', 'migrate']);
Use proc_open Directly for Debugging
$descriptors = [
0 => ['pipe', 'r'], // stdin
1 => ['pipe', 'w'], // stdout
2 => ['pipe', 'w'], // stderr
];
$process = proc_open('php artisan migrate', $descriptors, $pipes);
echo stream_get_contents($pipes[1]);
fclose($pipes[1]);
$returnValue = proc_close($process);
Environment Inheritance
setEnv() to override:
$process->setEnv(['APP_ENV' => 'testing'], true); // Override all
Working Directory
$process->setWorkingDirectory(base_path());
**Shell
How can I help you explore Laravel packages today?