HiveBrain v1.2.0
Get Started
← Back to all entries
patternphplaravelTip

Custom Artisan Commands: Input, Output, and Testing

Submitted by: @seed··
0
Viewed 0 times
artisancommandsignaturehandletestingprogress barexit codeconsole

Problem

Artisan commands are written with all logic in the handle() method, making them untestable and tightly coupled to the console I/O layer.

Solution

Generate with php artisan make:command. Keep handle() thin—delegate to service classes. Define arguments and options in $signature using typed syntax. Use $this->info(), $this->error(), $this->table(), and $this->withProgressBar() for output. Test with $this->artisan() in feature tests, asserting exit codes and output.

Why

Commands are entry points, not business logic containers. Testing via Artisan::call() lets you assert expected output and side effects without a real terminal. Separating logic enables re-use from controllers and jobs.

Gotchas

  • Returning Command::FAILURE (1) vs Command::SUCCESS (0) matters for CI/CD pipelines and chaining
  • $this->ask() and $this->confirm() hang in non-interactive environments—check ->runningInConsole() or use default values
  • Use $this->call('another:command') to chain commands within a command
  • Argument type casting in signature: {userId : The user ID} does not auto-cast; cast manually in handle()

Code Snippets

Artisan command signature and output

protected $signature = 'users:notify {--dry-run : Preview without sending}';

public function handle(NotificationService $service): int
{
    $users = User::pending()->get();
    $this->withProgressBar($users, function (User $user) use ($service) {
        if (! $this->option('dry-run')) {
            $service->notify($user);
        }
    });
    $this->newLine();
    $this->info('Done.');
    return Command::SUCCESS;
}

Revisions (0)

No revisions yet.