php-standard-library/process
Typed, non-blocking PHP API for spawning, monitoring, and controlling child processes. Manage stdin/stdout/stderr streams, retrieve exit codes, and handle timeouts and signals with a clean, reliable interface for long-running and parallel tasks.
Installation
composer require php-standard-library/process
No additional configuration is required—just autoload.
First Use Case: Running a Command in a Laravel Artisan Command
use PhpStandardLibrary\Process\Process;
class RunSystemCommand extends Command
{
protected $signature = 'app:run {command}';
public function handle()
{
$process = new Process($this->argument('command'));
$process->run();
$this->info($process->getOutput());
if (!$process->isSuccessful()) {
$this->error($process->getErrorOutput());
}
}
}
Run with:
php artisan app:run "ls -la"
Where to Look First
Process::run(), isSuccessful(), getOutput(), and getErrorOutput().Console/Kernel.php or custom commands for CLI tools.use PhpStandardLibrary\Process\Process;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
class ProcessJob implements ShouldQueue
{
use Dispatchable, Queueable;
public function handle()
{
$process = new Process(['git', 'pull']);
$process->setWorkingDirectory(base_path());
$process->run();
if (!$process->isSuccessful()) {
throw new RuntimeException($process->getErrorOutput());
}
}
}
Dispatch with:
ProcessJob::dispatch();
$process = new Process('tail -f storage/logs/laravel.log');
$process->start(); // Non-blocking
while ($process->isRunning()) {
$output = $process->getOutput();
if (!empty($output)) {
$this->line($output);
}
usleep(100000); // Throttle
}
$process = new Process('php artisan migrate');
$process->setEnv([
'APP_ENV' => 'testing',
'DB_CONNECTION' => 'sqlite',
]);
$process->run();
$process = new Process('php artisan queue:work --once');
$process->setTimeout(60); // 60 seconds
$process->run();
if ($process->isTimedOut()) {
$process->terminate();
$this->error('Process timed out!');
}
Bind the Process class to Laravel’s container for dependency injection:
// app/Providers/AppServiceProvider.php
public function register()
{
$this->app->bind(Process::class, function () {
return new Process(config('app.default_command'));
});
}
Use in controllers/commands:
public function __construct(private Process $process) {}
Create a facade for cleaner syntax:
// app/Facades/Process.php
namespace App\Facades;
use Illuminate\Support\Facades\Facade;
use PhpStandardLibrary\Process\Process;
class Process extends Facade
{
protected static function getFacadeAccessor()
{
return Process::class;
}
}
Usage:
Process::run('ls -la')->getOutput();
Listen for process completion in Laravel events:
// app/Listeners/ProcessCompletedListener.php
public function handle($event)
{
if (!$event->process->isSuccessful()) {
event(new ProcessFailed($event->process));
}
}
Blocking Calls in Synchronous Contexts
Process::run() blocks execution. If called in a route or middleware, it may time out.Process::start() + Process::wait() for async workflows or offload to queues.Output Buffering Issues
ffmpeg or docker may buffer output, causing getOutput() to return empty.--no-buffer flags or stream incrementally with start() + isRunning().Environment Variable Conflicts
setEnv() overwrites existing vars. Missing vars (e.g., PATH) can break commands.$env = array_merge($_ENV, ['CUSTOM_VAR' => 'value']);
$process->setEnv($env);
Working Directory Permissions
$process->setWorkingDirectory(storage_path('logs'));
chmod(storage_path('logs'), 0755);
Signal Handling Limitations
SIGKILL or SIGTERM. Forceful termination may leave zombie processes.Process::terminate() for graceful stops and handle failures:
if (!$process->isSuccessful()) {
$process->terminate();
throw new RuntimeException('Process failed');
}
Log Raw Output
Always log both stdout and stderr for debugging:
$this->info('STDOUT: ' . $process->getOutput());
$this->error('STDERR: ' . $process->getErrorOutput());
Check Exit Codes
Exit codes beyond 0 (success) or 1 (error) may indicate specific failures. Map them in your code:
switch ($process->getExitCode()) {
case 127: throw new RuntimeException('Command not found');
case 137: throw new RuntimeException('Process killed (OOM?)');
default: throw new RuntimeException('Unknown error');
}
Use strace for Low-Level Debugging
On Linux, trace system calls to diagnose hangs:
strace -f php artisan app:run "your-command"
Custom Process Builder
Extend the Process class to add Laravel-specific features:
class LaravelProcess extends Process
{
public function withLaravelEnv()
{
$this->setEnv([
'APP_ENV' => env('APP_ENV'),
'APP_KEY' => env('APP_KEY'),
]);
return $this;
}
}
Process Middleware Create middleware to validate commands or inject env vars:
class ProcessMiddleware
{
public function handle(Process $process, Closure $next)
{
if (str_contains($process->getCommand(), 'dangerous')) {
throw new \RuntimeException('Command blocked');
}
return $next($process);
}
}
Observability Hooks Add logging/metrics before/after process execution:
$process->onStart(function () {
Log::info('Process started', ['command' => $process->getCommand()]);
});
$process->onComplete(function () {
Log::info('Process completed', [
'exit_code' => $process->getExitCode(),
'duration' => $process->getDuration(),
]);
});
Windows-Specific Paths
/), not backslashes (\), in commands to avoid issues:
$process = new Process(['cmd', '/c', 'dir', 'C:/path/to/dir']);
Shebang Lines
#!/usr/bin/env php) is executable:
chmod('path/to/script', 0755);
$process = new Process(['./path/to/script']);
Resource Limits
max_execution_time. Adjust via:
ini_set('max_execution_time', 300); // 5 minutes
$process->run();
How can I help you explore Laravel packages today?