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.
## Technical Evaluation
### **Architecture Fit**
- **Use Case Alignment**: The package remains a strong fit for **structured shell command execution** in Laravel, particularly for **CLI automation, build pipelines, and system integrations**. The 6.1.1 release introduces **streaming output support**, which enhances its utility for **real-time logging** (e.g., Docker builds, Git operations) and **interactive CLI tools** within Laravel’s console kernel or Artisan commands.
- **API Design**: The addition of **`stream()` method** and **`onOutput()` callback** aligns with modern PHP practices, reducing boilerplate for **live output handling**. This is especially valuable for Laravel apps where **progress feedback** (e.g., `php artisan deploy:watch`) is critical. The structured API (e.g., `stdout`, `stderr`, `exitCode`) continues to improve readability and maintainability.
- **Security Focus**: The package’s **argument escaping/quoting** and **environment isolation** remain unchanged, preserving its suitability for Laravel apps interfacing with external systems. The new release does **not introduce security regressions**, though the streaming feature requires careful handling of **sensitive data in real-time output**.
### **Integration Feasibility**
- **Laravel Compatibility**: The package remains **dependency-light** and integrates seamlessly with Laravel’s **console kernel, Artisan commands, queued jobs, or service containers**. The new streaming API is particularly useful for **Laravel Livewire** or **Inertia.js** apps where real-time CLI feedback is displayed to users (e.g., a deployment dashboard).
- **Cross-Platform Support**: Streaming output may introduce **Windows-specific quirks** (e.g., `cmd.exe` buffering vs. `bash` line-by-line streaming). Laravel’s cross-platform tooling (e.g., `php artisan serve`) could benefit, but **testing is required** to ensure consistent behavior across OSes.
- **Async/Blocking Considerations**: The streaming feature **does not resolve the async/blocking challenge**—commands still execute synchronously by default. For async use cases, the package should still be wrapped in a **queueable job** or **process manager** (e.g., Symfony Process + Laravel Horizon).
### **Technical Risk**
- **Dependency Isolation**: The package’s **lack of stars/activity** remains a risk. The 6.1.1 release is a **minor update**, suggesting low urgency for a fork, but **long-term maintenance** is still uncertain. A **custom wrapper** may still be preferable for critical systems.
- **Performance Overhead**: Streaming output introduces **additional memory/CPU usage** for buffering and callbacks. Benchmarking against raw `proc_open()` is **more critical** than before, especially for **high-frequency commands** (e.g., real-time monitoring).
- **Error Handling Granularity**: The new `onOutput()` callback could **complicate error handling** if not managed carefully. Laravel’s `throw_if` may need adaptation to handle **streaming failures** (e.g., partial output before a crash).
- **Environment Variable Leakage**: Streaming output could **expose sensitive data** (e.g., passwords in `stdout`) if not filtered. Laravel’s `env()` helper should be used **selectively** for streaming commands.
### **Key Questions**
1. **Adoption Risk**: Does the **new streaming feature** justify using this package over alternatives (e.g., Symfony Process)? Would a **custom wrapper** be needed to address potential quirks?
2. **Async Strategy**: How will **streaming output** be handled in async contexts (e.g., queued jobs)? Will **buffering** or **chunked storage** (e.g., database) be required?
3. **Cross-Platform Testing**: Are there **Windows-specific issues** with streaming (e.g., `cmd.exe` buffering)? Should a **fallback mechanism** be implemented?
4. **Security Review**: Does the streaming API **preserve argument escaping** for dynamic commands? Are there risks of **sensitive data leakage** in real-time output?
5. **Monitoring**: How will **streaming failures** (e.g., partial output) be logged/alerted in Laravel’s **Monolog** or **Sentry** setup?
6. **Backward Compatibility**: Does 6.1.1 introduce **breaking changes** for existing Laravel integrations? (e.g., method signatures, exception types)
---
## Integration Approach
### **Stack Fit**
- **Laravel Console**: The streaming feature is **ideal for Artisan commands** requiring real-time feedback (e.g., `php artisan deploy:stream` for live Docker logs).
- **Queued Jobs**: For async use, streaming output should be **buffered and stored** (e.g., database, Redis) for later retrieval, rather than processed in real-time.
- **Service Layer**: Centralize shell logic in a **service class** (e.g., `SystemCommandService`) with **streaming-aware methods** (e.g., `streamGitLogs()`).
- **Testing**: The new API simplifies **mocking streaming output** in PHPUnit, but **edge cases** (e.g., partial output) require additional test coverage.
### **Migration Path**
1. **Phase 1: Replace Raw Calls**
- Replace `exec()`, `shell_exec()`, or `proc_open()` with the package’s API, focusing on **non-streaming commands** first.
- Example:
```php
// Before (non-streaming)
$result = $shell->run('git pull')->getStdout();
// After (streaming)
$shell->run('git pull')->stream(function ($output) {
echo $output; // Real-time feedback
});
```
2. **Phase 2: Centralize Logic**
- Create a **facade/service** (e.g., `app/SystemCommand.php`) with **streaming and non-streaming methods**.
- Example:
```php
class SystemCommand {
public function __construct(private Shell $shell) {}
public function streamDockerLogs(string $container): void {
$this->shell->run("docker logs $container")->stream(fn($line) => Log::info($line));
}
}
```
3. **Phase 3: Async Wrapping**
- For long-running streaming commands, use a **hybrid approach**:
- **Store chunks** in a queue (e.g., Redis) during execution.
- **Process later** via a Laravel job (e.g., `ProcessStreamedOutput::dispatch()`).
- Example:
```php
class StreamToQueue implements ShouldQueue {
public function handle(Shell $shell) {
$shell->run('docker build .')->stream(fn($chunk) => Queue::push(new StoreChunk($chunk)));
}
}
```
### **Compatibility**
- **Laravel Versions**: Compatible with **Laravel 10+** (PHP 8.1+). No breaking changes in 6.1.1, but **streaming may require PHP 8.1+ features** (e.g., closures).
- **PHP Extensions**: No additional extensions needed, but **streaming may benefit from `pcntl` for process control** (optional).
- **Environment Variables**: Streaming output could **conflict with Laravel’s `.env`** if not filtered. Use `array_filter()` to exclude sensitive vars:
```php
$shell->run('command')->withEnvironment(array_merge([
'KEY' => 'value',
], array_filter($_ENV, fn($key) => !str_starts_with($key, 'SENSITIVE_'))));
| Step | Task | Dependencies |
|---|---|---|
| 1 | Add package via Composer (6.1.1) |
None |
| 2 | Replace exec() calls in non-streaming Artisan commands |
Package installed |
| 3 | Create service layer with streaming methods | Step 2 complete |
| 4 | Test streaming on Linux/Windows (edge cases) | Step 3 complete |
| 5 | Implement async buffering for streaming jobs | Laravel Queues configured |
| 6 | Add monitoring/logging for streaming failures | Step 5 complete |
| 7 | Roll out streaming features in canary commands | Steps 1–6 complete |
onOutput() callbacks).Log::info($streamChunk)).6.1.1).$shell->run('long-command')->timeout(6
How can I help you explore Laravel packages today?