php-standard-library/shell
Execute shell commands safely in PHP with robust argument escaping, clear capture of stdout/stderr, and helpful error handling. Part of PHP Standard Library, designed for predictable command execution and output management across environments.
Installation
composer require php-standard-library/shell
No additional configuration is required—just autoload the package.
First Command Execution
use PhpStandardLibrary\Shell\Command;
$output = Command::run('ls -la')->getOutput();
echo $output;
Command::run(string $command) → Returns a CommandResult object.$result->getOutput() → Captures stdout.$result->getErrorOutput() → Captures stderr.$result->getExitCode() → Returns the exit code (0 = success).First Use Case: File Operations
$result = Command::run('chmod 755 storage/logs/app.log')->getExitCode();
if ($result !== 0) {
throw new RuntimeException("Failed to change permissions");
}
Command Construction
$command = Command::create('git')
->addArgument('status')
->addOption('-u', '--untracked-files=no')
->setWorkingDirectory(base_path());
addArgument(string $arg) → Appends a positional argument.addOption(string $short, string $long) → Adds a flag (e.g., -u, --untracked-files).setWorkingDirectory(string $path) → Changes the working directory.setEnvironment(array $env) → Overrides environment variables (e.g., ['PATH' => '/custom/path']).Execution with Context
$result = $command->run();
if ($result->isSuccessful()) {
echo $result->getOutput(); // stdout
} else {
log($result->getErrorOutput()); // stderr
throw new RuntimeException("Command failed with exit code: {$result->getExitCode()}");
}
Pipes and Redirections
$result = Command::run('cat file.txt | grep "error"')
->pipeTo('wc -l'); // Chains commands
$command->redirectOutputTo('output.log') → Redirects stdout to a file.$command->redirectErrorTo('error.log') → Redirects stderr to a file.Artisan Command Wrapper
use Illuminate\Support\Facades\Artisan;
use PhpStandardLibrary\Shell\Command;
$result = Command::run('php artisan cache:clear');
if (!$result->isSuccessful()) {
throw new \RuntimeException("Cache clear failed");
}
Queueable Shell Jobs
use PhpStandardLibrary\Shell\Command;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
class BackupDatabase implements ShouldQueue
{
use Queueable;
public function handle()
{
$result = Command::run('mysqldump -u user -p db_name > backup.sql');
if (!$result->isSuccessful()) {
throw new \RuntimeException("Backup failed");
}
}
}
Service Provider Bootstrapping
public function boot()
{
$this->app->singleton('shell', function () {
return new class {
public function run(string $command)
{
return Command::run($command);
}
};
});
}
public function __construct(private ShellService $shell) {}
Argument Escaping
Command::escapeArgument() or let the package handle it:
$command = Command::create('cp')
->addArgument(Command::escapeArgument('/user/input'))
->addArgument(Command::escapeArgument('/user/output'));
addArgument() directly—it escapes by default.Exit Code Misinterpretation
grep) return non-zero even on "success" if no matches are found.stderr or customize success logic:
if ($result->getExitCode() !== 0 && strpos($result->getErrorOutput(), 'No such file') === false) {
throw new \RuntimeException("Command failed");
}
Environment Variable Leaks
DB_PASSWORD) in stderr.$command->setEnvironment(['DB_HOST' => 'localhost']);
Blocking Operations
docker build) block the request.ignoreOutput() for fire-and-forget:
Command::run('docker build -t app .')->ignoreOutput();
Verbose Output Enable debug mode to log raw commands:
Command::setDebugMode(true); // Logs all executed commands to storage/logs/shell.log
Dry Runs Simulate commands without execution:
$command = Command::create('ls -la');
echo $command->getCommandString(); // Outputs: "ls -la"
Timeout Handling Set a timeout to avoid hanging:
$result = Command::run('sleep 10')
->setTimeout(5) // Fails after 5 seconds
->run();
Custom Command Builders
Extend the Command class for domain-specific logic:
class GitCommand extends Command
{
public function pull()
{
return $this->addArgument('pull')->run();
}
}
Result Decorators
Wrap CommandResult to add domain logic:
class GitResult extends CommandResult
{
public function hasChanges(): bool
{
return str_contains($this->getOutput(), 'modified:');
}
}
Plugin System Register global command modifiers in a service provider:
Command::macro('withSudo', function () {
return $this->prependArgument('sudo');
});
Usage:
Command::create('chmod')->withSudo()->addArgument('755 file.txt')->run();
Logging Integration Hook into Laravel’s logging:
Command::setLogger(function ($message) {
\Log::debug($message);
});
Command::batch() to reduce process overhead:
$batch = Command::batch();
$batch->add('command1');
$batch->add('command2');
$results = $batch->run(); // Runs sequentially in one process
Command instance:
$command = Command::create('php artisan');
foreach ($jobs as $job) {
$command->resetArguments()->addArgument("job:run {$job}");
$result = $command->run();
}
How can I help you explore Laravel packages today?