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

Console Extra Laravel Package

zenstruck/console-extra

Modular helpers to reduce boilerplate in Symfony console commands. Build invokable commands with auto-injected IO, arguments/options via attributes, and handy traits to run other commands or processes. Designed to mix and match features or power a shared base command.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps

  1. Installation:

    composer require zenstruck/console-extra
    
  2. First Use Case: Create a simple command extending InvokableServiceCommand with auto-injected IO and a service:

    use Zenstruck\Console\InvokableServiceCommand;
    use Zenstruck\Console\IO;
    use App\Services\UserService;
    
    #[AsCommand('app:greet')]
    class GreetCommand extends InvokableServiceCommand
    {
        public function __invoke(IO $io, UserService $userService): void
        {
            $io->success($userService->getGreeting());
        }
    }
    

    Run it:

    php artisan app:greet
    
  3. Key Files to Explore:

    • app/Console/Commands/ (for command classes)
    • config/services.php (for service bindings)
    • app/Providers/AppServiceProvider.php (for registering traits like RunsCommands)

Implementation Patterns

Core Workflows

1. Command Development Pattern

  • Base Command: Extend InvokableServiceCommand for all app commands to leverage auto-wiring and IO.
    abstract class AppCommand extends InvokableServiceCommand
    {
        use RunsCommands, RunsProcesses;
    }
    
  • Attribute-Driven Configuration: Use #[Argument] and #[Option] on __invoke() parameters.
    public function __invoke(
        #[Argument] string $name,
        #[Option] bool $force = false
    ): void { ... }
    

2. Service Integration

  • Auto-Wiring: Inject services directly into __invoke().
    public function __invoke(IO $io, UserRepository $users, LoggerInterface $logger): void { ... }
    
  • DI Attributes: Use Symfony’s DI attributes for complex dependencies.
    public function __invoke(
        #[Autowire('%env(APP_NAME)%')] string $appName,
        #[TaggedIterator('app.event_listener')] iterable $listeners
    ): void { ... }
    

3. Command Orchestration

  • Nested Commands: Use RunsCommands trait to chain commands.
    public function __invoke(): void
    {
        $this->runCommand('migrate:fresh');
        $this->runCommand('db:seed', ['--class' => 'UserSeeder']);
    }
    
  • Process Execution: Use RunsProcesses for shell scripts.
    public function __invoke(): void
    {
        $this->runProcess(['php', 'artisan', 'queue:work', '--once']);
    }
    

4. Interactive Commands

  • Input Handling: Use IO for user prompts and validation.
    public function __invoke(IO $io): void
    {
        $confirm = $io->confirm('Proceed?', false);
        if (!$confirm) return;
        $this->runCommand('deploy:production');
    }
    

5. Command Runner in Controllers

  • External Execution: Run commands from HTTP routes.
    use Zenstruck\Console\CommandRunner;
    
    public function deploy(Request $request)
    {
        CommandRunner::from(app('console'), 'deploy:production')->run();
        return response()->json(['status' => 'deployed']);
    }
    

Integration Tips

Laravel-Specific

  1. Service Provider Setup: Register the CommandSummarySubscriber in AppServiceProvider:

    public function boot(): void
    {
        $this->app->make('command.bus')->getDispatcher()->addSubscriber(
            new \Zenstruck\Console\EventListener\CommandSummarySubscriber()
        );
    }
    
  2. Command Registration: Use #[AsCommand] for auto-registration (Laravel 10+):

    #[AsCommand(name: 'app:custom', description: 'Custom app command')]
    class CustomCommand extends InvokableServiceCommand { ... }
    
  3. Artisan Integration: Extend Laravel’s Command class for hybrid usage:

    use Illuminate\Console\Command;
    use Zenstruck\Console\IO;
    
    class HybridCommand extends Command
    {
        protected function execute(InputInterface $input, OutputInterface $output): int
        {
            $io = new IO($input, $output);
            // Use $io for styled output
            return 0;
        }
    }
    

Performance

  • Batch Processing: Use runCommand() with --batch for non-interactive runs.
  • Output Suppression: Redirect output to null for silent execution:
    $this->runCommand('queue:work --once', [], null, new \Symfony\Component\Console\Output\NullOutput());
    

Testing

  • Mocking IO:
    $io = $this->createMock(IO::class);
    $io->method('success')->willReturnCallback(fn($msg) => $this->assertEquals('Expected', $msg));
    $command = new MyCommand();
    $command->setIo($io);
    $command->__invoke($io);
    
  • Command Runner Tests:
    $this->assertSame(0, CommandRunner::for($command)->run('--arg=value'));
    

Gotchas and Tips

Pitfalls

  1. Trait Overuse:

    • Issue: Adding RunsCommands or RunsProcesses to every command bloats memory.
    • Fix: Use a base command class (e.g., AppCommand) with shared traits.
      abstract class AppCommand extends InvokableServiceCommand
      {
          use RunsCommands, RunsProcesses;
      }
      
  2. Attribute Conflicts:

    • Issue: Mixing #[Argument] on class level and parameter level causes conflicts.
    • Fix: Prefer parameter-level attributes for clarity:
      public function __invoke(
          #[Argument] string $name, // Defines argument here
      ): void { ... }
      
  3. Process Timeouts:

    • Issue: runProcess() throws RuntimeException on failure, which may break command flow.
    • Fix: Catch exceptions or use Process::mustRun() for explicit control.
      try {
          $this->runProcess(['long-running-script']);
      } catch (\RuntimeException $e) {
          $this->io()->error('Process failed: ' . $e->getMessage());
      }
      
  4. IO Injection:

    • Issue: Forgetting to inject IO in __invoke() loses styled output methods.
    • Fix: Always include IO $io as the first parameter for consistency.
  5. Command Summary:

    • Issue: Summary subscriber adds overhead to every command.
    • Fix: Disable in production or use conditionally:
      if (app()->environment('local')) {
          $dispatcher->addSubscriber(new CommandSummarySubscriber());
      }
      

Debugging Tips

  1. Command Arguments:

    • Use var_dump($this->input()) to inspect raw input in __invoke().
  2. Process Output:

    • Enable verbose mode with -v to see hidden process output:
      php artisan my:command -v
      
  3. Dependency Injection:

    • Check for circular dependencies if services fail to inject:
      php artisan container:debug
      
  4. Attribute Validation:

    • Use PHPStan to catch invalid attribute usage:
      vendor/bin/phpstan analyse --level=5 app/Console/Commands
      

Extension Points

  1. Custom IO Methods:

    • Extend IO to add app-specific helpers:
      class AppIO extends IO
      {
          public function tableWithHeader(array $rows, string $header): void
          {
              $this->section($header);
              $this->table($rows);
          }
      }
      
  2. Command Decorators:

    • Wrap commands to add pre/post hooks:
      class LoggingCommandDecorator
      {
          public function __invoke(InvokableServiceCommand $command, IO $io)
          {
              $start = microtime(true);
              $result = $command($io);
              $io->text(sprintf('Executed in %.2fms', (microtime(true) - $start) * 1000));
              return $result;
          }
      }
      
  3. Dynamic Suggestions:

    • Fetch suggestions from external sources (e.g., API):
      #[Option(suggestions: 'getUserRoles')]
      public function __invoke(IO $io, array $roles): void { ... }
      
      private function getUserRoles(): array
      {
          return $this->userRepository->getAvailableRoles();
      }
      
  4. Command Metadata:

    • Use #[AsCommand] to centralize command metadata (e.g., categories):
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.
bugban/php-sdk
littlerocket/job-queue-bundle
graham-campbell/flysystem
capell-app/block-library
axium/identity
cetria/laravel-dummy-models
cetria/reflection-helper
agropredict/sso-auth-bundle
evolvestudio/spam-protection
directorytree/opensearch-client
directorytree/opensearch-adapter
datacore/hub-sdk
develia/commons
cuci/prototurk-sdk
cuci/prototurk-sdk-symfony
develia/geo-bundle
dreamzy/livewire-charts
touchestate-sdk/php-sdk
22h/doctrine-garbage-collection-bundle
agtp/agtp-php