patternphplaravelTip
Custom Artisan Commands: Input, Output, and Testing
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.