Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Shell Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation

    composer require php-standard-library/shell
    

    No additional configuration is required—just autoload the package.

  2. First Command Execution

    use PhpStandardLibrary\Shell\Command;
    
    $output = Command::run('ls -la')->getOutput();
    echo $output;
    
    • Key methods:
      • Command::run(string $command) → Returns a CommandResult object.
      • $result->getOutput() → Captures stdout.
      • $result->getErrorOutput() → Captures stderr.
      • $result->getExitCode() → Returns the exit code (0 = success).
  3. 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");
    }
    

Implementation Patterns

Core Workflow: Structured Command Execution

  1. Command Construction

    $command = Command::create('git')
        ->addArgument('status')
        ->addOption('-u', '--untracked-files=no')
        ->setWorkingDirectory(base_path());
    
    • Methods:
      • 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']).
  2. 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()}");
    }
    
  3. Pipes and Redirections

    $result = Command::run('cat file.txt | grep "error"')
        ->pipeTo('wc -l'); // Chains commands
    
    • Alternatives:
      • $command->redirectOutputTo('output.log') → Redirects stdout to a file.
      • $command->redirectErrorTo('error.log') → Redirects stderr to a file.

Integration with Laravel

  1. 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");
    }
    
  2. 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");
            }
        }
    }
    
  3. Service Provider Bootstrapping

    public function boot()
    {
        $this->app->singleton('shell', function () {
            return new class {
                public function run(string $command)
                {
                    return Command::run($command);
                }
            };
        });
    }
    
    • Inject via constructor:
      public function __construct(private ShellService $shell) {}
      

Gotchas and Tips

Pitfalls

  1. Argument Escaping

    • Risk: Unescaped arguments can lead to command injection.
    • Fix: Use Command::escapeArgument() or let the package handle it:
      $command = Command::create('cp')
          ->addArgument(Command::escapeArgument('/user/input'))
          ->addArgument(Command::escapeArgument('/user/output'));
      
    • Alternative: Use addArgument() directly—it escapes by default.
  2. Exit Code Misinterpretation

    • Gotcha: Some commands (e.g., grep) return non-zero even on "success" if no matches are found.
    • Fix: Check stderr or customize success logic:
      if ($result->getExitCode() !== 0 && strpos($result->getErrorOutput(), 'No such file') === false) {
          throw new \RuntimeException("Command failed");
      }
      
  3. Environment Variable Leaks

    • Risk: Accidentally exposing sensitive env vars (e.g., DB_PASSWORD) in stderr.
    • Fix: Explicitly set only required env vars:
      $command->setEnvironment(['DB_HOST' => 'localhost']);
      
  4. Blocking Operations

    • Gotcha: Long-running commands (e.g., docker build) block the request.
    • Fix: Use Laravel Queues or ignoreOutput() for fire-and-forget:
      Command::run('docker build -t app .')->ignoreOutput();
      

Debugging Tips

  1. Verbose Output Enable debug mode to log raw commands:

    Command::setDebugMode(true); // Logs all executed commands to storage/logs/shell.log
    
  2. Dry Runs Simulate commands without execution:

    $command = Command::create('ls -la');
    echo $command->getCommandString(); // Outputs: "ls -la"
    
  3. Timeout Handling Set a timeout to avoid hanging:

    $result = Command::run('sleep 10')
        ->setTimeout(5) // Fails after 5 seconds
        ->run();
    

Extension Points

  1. Custom Command Builders Extend the Command class for domain-specific logic:

    class GitCommand extends Command
    {
        public function pull()
        {
            return $this->addArgument('pull')->run();
        }
    }
    
  2. Result Decorators Wrap CommandResult to add domain logic:

    class GitResult extends CommandResult
    {
        public function hasChanges(): bool
        {
            return str_contains($this->getOutput(), 'modified:');
        }
    }
    
  3. 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();
    
  4. Logging Integration Hook into Laravel’s logging:

    Command::setLogger(function ($message) {
        \Log::debug($message);
    });
    

Performance

  • Batch Commands: For multiple commands, use Command::batch() to reduce process overhead:
    $batch = Command::batch();
    $batch->add('command1');
    $batch->add('command2');
    $results = $batch->run(); // Runs sequentially in one process
    
  • Reuse Processes: For repeated commands (e.g., in loops), reuse the Command instance:
    $command = Command::create('php artisan');
    foreach ($jobs as $job) {
        $command->resetArguments()->addArgument("job:run {$job}");
        $result = $command->run();
    }
    
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
davejamesmiller/laravel-breadcrumbs
artisanry/parsedown
christhompsontldr/phpsdk
enqueue/dsn
bunny/bunny
enqueue/test
enqueue/null
enqueue/amqp-tools
milesj/emojibase
bower-asset/punycode
bower-asset/inputmask
bower-asset/jquery
bower-asset/yii2-pjax
laravel/nova
spatie/laravel-mailcoach
spatie/laravel-superseeder
laravel/liferaft
nst/json-test-suite
danielmiessler/sec-lists
jackalope/jackalope-transport