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—autoloads via Composer.
First Use Case: Running a Command in a Laravel Artisan Command
use PhpStandardLibrary\Process\Process;
class ExampleCommand extends Command
{
protected $signature = 'example:run-command';
public function handle()
{
$process = new Process(['ls', '-la']);
$process->run();
if ($process->isSuccessful()) {
$this->info($process->getOutput());
} else {
$this->error($process->getErrorOutput());
}
}
}
Run with:
php artisan example:run-command
Key Methods to Explore First
Process::run() – Execute the command synchronously.Process::start() – Start asynchronously (non-blocking).Process::isSuccessful() – Check exit code (returns true if exit code is 0).Process::getOutput() / Process::getErrorOutput() – Retrieve streams.Process::setTimeout() – Set a timeout in seconds (default: null = no timeout).use PhpStandardLibrary\Process\Process;
class DeployCommand extends Command
{
protected $signature = 'deploy:run-migrations';
public function handle()
{
$process = new Process(['php', 'artisan', 'migrate', '--force']);
$process->setWorkingDirectory(base_path());
$process->run();
if (!$process->isSuccessful()) {
throw new RuntimeException(
"Migrations failed: " . $process->getErrorOutput()
);
}
}
}
use PhpStandardLibrary\Process\Process;
class ProcessJob implements ShouldQueue
{
public function handle()
{
$process = new Process(['docker', 'build', '-t', 'app', '.']);
$process->start(); // Non-blocking
while ($process->isRunning()) {
$output = $process->getOutput();
if (!empty($output)) {
Log::info($output); // Stream output in real-time
}
usleep(100000); // Throttle
}
if (!$process->isSuccessful()) {
Log::error($process->getErrorOutput());
throw new ProcessFailedException($process->getErrorOutput());
}
}
}
use PhpStandardLibrary\Process\Process;
class TestCommand extends Command
{
protected $signature = 'test:run';
public function handle()
{
$process = new Process(['phpunit']);
$process->setEnv([
'APP_ENV' => 'testing',
'DB_CONNECTION' => 'sqlite',
]);
$process->run();
$this->line($process->getOutput());
}
}
use PhpStandardLibrary\Process\Process;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
class ProcessImageJob implements ShouldQueue
{
use Queueable;
public function handle()
{
$process = new Process(['ffmpeg', '-i', 'input.mp4', 'output.mp4']);
$process->setTimeout(300); // 5 minutes
$process->run();
if (!$process->isSuccessful()) {
throw new RuntimeException(
"FFmpeg failed: " . $process->getErrorOutput()
);
}
}
}
Leverage Laravel’s Service Container
Bind the Process class for dependency injection:
$this->app->bind(Process::class, function () {
return new Process(config('app.default_command'));
});
Then inject it into controllers/commands:
public function __construct(private Process $process) {}
Use Facades for Convenience (Optional) Create a facade to simplify usage:
// app/Providers/AppServiceProvider.php
use Illuminate\Support\Facades\Facade;
use PhpStandardLibrary\Process\Process;
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
Facade::register('Process', function () {
return new Process(config('app.default_command'));
});
}
}
Now use Process::run() directly in views/controllers.
Handle Timeouts Gracefully Always set a timeout to avoid hanging:
$process->setTimeout(60); // 60 seconds
$process->run();
if ($process->isTimedOut()) {
Log::warning('Process timed out');
}
Log Process Output for Debugging Use Laravel’s logging to capture output:
$process->run();
Log::debug('Process Output:', ['output' => $process->getOutput()]);
Log::debug('Process Errors:', ['errors' => $process->getErrorOutput()]);
Validate Commands Before Execution Sanitize user-provided commands to prevent injection:
$command = escapeshellcmd($userInput);
$process = new Process(explode(' ', $command));
Blocking Calls by Default
run() blocks execution until the process completes. For long-running tasks, use start() + isRunning() loop.start() for async workflows and poll with isRunning().Output Buffering Issues
ffmpeg, docker) buffer output. Use --no-buffer flags or stream incrementally.ffmpeg, use -loglevel info or pipe output to a file:
$process = new Process(['ffmpeg', '-i', 'input.mp4', '-loglevel', 'info', 'output.mp4']);
Environment Variables Not Merged
setEnv() replaces existing variables. Explicitly pass all required environment variables.$process->setEnv(array_merge(
$_ENV,
['CUSTOM_VAR' => 'value']
));
Working Directory Permissions
chmod or adjust umask:
$process->setWorkingDirectory(storage_path('logs'));
umask(0000); // Ensure full permissions
No Built-in Retry Logic
$retries = 3;
while ($retries--) {
$process->run();
if ($process->isSuccessful()) break;
sleep(2 ** $retries); // Exponential backoff
}
Signal Handling Limitations
SIGKILL or SIGTERM. Use terminate() for graceful shutdowns.proc_terminate() (if available) or wrap in a pcntl_signal handler.Cross-Platform Path Issues
C:\path\to\file may fail on Unix. Normalize paths:str_replace('\\', '/', $path) or DIRECTORY_SEPARATOR.Check Exit Codes Always log the exit code for debugging:
$process->run();
Log::debug('Exit Code:', ['code' => $process->getExitCode()]);
Enable Verbose Output For troubleshooting, enable verbose logging:
$process = new Process(['command', '--verbose']);
Use strace or Process Explorer
On Linux, use strace to debug process behavior:
strace -f -o trace.log php artisan your:command
On Windows, use Process Explorer to monitor child processes.
Test with Simple Commands First
Start with a simple command like echo "hello" to verify the package works before complex commands.
class GitProcess extends Process
{
public function __construct(string $repoPath)
{
parent::__construct(['git
How can I help you explore Laravel packages today?