symfony/process
Symfony Process component runs external commands in sub-processes, with built-in support for piping input/output, timeouts, signals, and error handling. Ideal for safely launching CLI tools from PHP applications and capturing their output.
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(), getErrorOutput(), isSuccessful().$process->setEnv(['KEY' => 'VALUE']) or $process->setEnv($arrayOfEnvs).$process = new Process(['git', 'status']);
$process->run();
// Incremental output (streaming)
$process->setTimeout(3600); // 1-hour timeout
$process->start();
while ($process->isRunning()) {
echo $process->getOutput();
usleep(100); // Throttle to avoid CPU overload
}
$envs = [
'DB_HOST' => env('DB_HOST'),
'APP_ENV' => 'testing',
'ARRAY_VAR' => json_encode(['key' => 'value']), // Pass complex data
];
$process->setEnv($envs);
$process->inheritEnvironmentVariables(false); // Disable inheritance
$process->setEnv(['MY_VAR' => 'custom_value']);
if ($process->isSuccessful()) {
// Success logic
} else {
throw new \RuntimeException(
"Command failed with exit code {$process->getExitCode()}. Error: {$process->getErrorOutput()}"
);
}
try {
$process->mustRun(); // Throws ProcessFailedException on failure
} catch (ProcessFailedException $e) {
report($e);
}
use Symfony\Component\Process\Process;
use Illuminate\Console\Command;
class DeployCommand extends Command {
protected $signature = 'deploy:run';
public function handle() {
$process = new Process(['./vendor/bin/deployer', 'deploy']);
$process->run();
$this->output->writeln($process->getOutput());
}
}
class ProcessService {
public function run(string $command, array $env = []): string {
$process = new Process(explode(' ', $command));
$process->setEnv($env);
$process->run();
return $process->getOutput();
}
}
$process = new Process(['tail', '-f', 'app.log']);
$process->start();
while ($process->isRunning()) {
$output = $process->getOutput();
if (!empty($output)) {
$this->log($output); // Custom logger (e.g., Laravel Log)
}
usleep(250000); // 0.25s delay
}
$process = new Process(['php', 'artisan', 'queue:work', '--timeout=60']);
$process->setTimeout(300); // 5-minute timeout
$process->run();
$command = PHP_OS === 'WINNT'
? ['powershell', '-command', 'Get-ChildItem']
: ['ls', '-la'];
$process = new Process($command);
$grep = new Process(['grep', 'error']);
$tail = new Process(['tail', '-n', '100', 'app.log']);
$grep->setInput($tail->getOutput());
$grep->run();
.env integration):
$validEnvKeys = ['APP_ENV', 'DB_HOST', 'QUEUE_CONNECTION'];
$env = array_filter(envToArray(), fn($key) => in_array($key, $validEnvKeys));
$process->setEnv($env);
use Symfony\Component\Process\Process;
$processes = [];
for ($i = 0; $i < 5; $i++) {
$processes[] = new Process(['php', 'artisan', 'queue:work', '--queue=high']);
$processes[$i]->start();
}
foreach ($processes as $process) {
$process->wait();
}
use Symfony\Component\Process\Process;
use Symfony\Component\Messenger\MessageBus;
use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
$message = new RunProcessMessage(
command: ['php', 'artisan', 'queue:work'],
env: ['QUEUE_CONNECTION' => 'database'],
timeout: 300
);
$bus->dispatch($message);
$command = [
'php',
'artisan',
'migrate',
'--database=' . env('DB_CONNECTION'),
'--force'
];
$process = new Process($command);
Environment Variable Limits (Windows)
json_encode()) may fail.setEnv() with stringified data or split into multiple processes.
if (PHP_OS === 'WINNT' && strlen(json_encode($env)) > 32000) {
throw new \RuntimeException('Windows env limit exceeded. Split or stringify data.');
}
Stdin/Stdout Corruption
mustRun() or check isRunning() before reading output.
if (!$process->isRunning()) {
throw new \RuntimeException('Process terminated unexpectedly.');
}
CGI/FastCGI Context Leaks
$_SERVER vars), causing security risks.inheritEnvironmentVariables(false) or clear sensitive vars.
$process->inheritEnvironmentVariables(false);
$process->setEnv(array_filter($_ENV, fn($key) => !str_starts_with($key, 'HTTP_'), ARRAY_FILTER_USE_KEY));
Array Environment Variables
setEnv() are stringified automatically, but malformed data may cause parsing errors in subprocesses.$env = ['CONFIG' => json_encode(['key' => 'value'])];
$process->setEnv($env);
Timeout Handling
setTimeout() kills the process abruptly, which may leave locks/files in a bad state.$process->setTimeout(60);
try {
$process->mustRun();
} catch (ProcessTimedOutException $e) {
$this->retryOrFail($process);
}
Cross-Platform Paths
\, Linux/macOS /. Hardcoded paths may fail.DIRECTORY_SEPARATOR.
$command = ['php', str_replace('/', DIRECTORY_SEPARATOR, 'path/to/script.php')];
PTY Mode Quirks
usePty(true) can mix stdout/stderr output unpredictably.How can I help you explore Laravel packages today?