Command registration and dispatch
Concept
Command registration and dispatch is how the console Application knows which commands exist and how it routes php artisan <command-name> to the correct class.
Registration: Commands are added to the Application using add(Command $command). The application stores them by name: ['make:model' => MakModelCommand, 'migrate' => MigrateCommand, ...].
Dispatch: Application::run() parses argv to extract the command name (the first argument after the script name). It looks up the command in its registry and calls command->run($input, $output).
Command discovery patterns:
- Manual registration: Explicitly call
$app->add(new MakeModelCommand())for each command. - Kernel registration:
$kernel->commands([MakeModelCommand::class, MigrateCommand::class]). - Auto-discovery: Scan a directory (e.g.,
app/Console/Commands/), find all classes extendingCommand, resolve from the container. - Service providers: Commands registered in a service provider's
boot()method.
The help command: php artisan help <command> prints the command's description, arguments, and options. Built by parsing the signature.
The list command: php artisan list shows all registered commands, grouped by namespace (the part before :). Auto-generated from the command registry.
Exit code propagation: The command returns an int from handle(). The Application::run() returns that int. The shell receives it. exit(0) = success. exit(1) = failure. CI/CD systems check this.
Code Example
<?php
namespace Framework\Console;
class Application
{
private array $commands = [];
public function __construct(
private readonly \Framework\Container\Container $container,
private readonly string $name = 'Console',
private readonly string $version = '1.0.0',
) {
$this->registerBuiltInCommands();
}
public function add(Command $command): static
{
$this->commands[$command->getName()] = $command;
return $this;
}
public function addCommands(array $commandClasses): static
{
foreach ($commandClasses as $class) {
$this->add($this->container->make($class));
}
return $this;
}
public function run(): int
{
$argv = $_SERVER['argv'] ?? [];
$commandName = $argv[1] ?? 'list';
if (in_array($commandName, ['-h', '--help', 'help'])) {
$commandName = 'help';
}
if (!isset($this->commands[$commandName])) {
fwrite(STDERR, "Command [{$commandName}] not found.\n");
$this->listCommands();
return 1;
}
$command = $this->commands[$commandName];
$input = new ArgvInput(array_slice($argv, 2), $command->getSignature());
$output = new ConsoleOutput();
try {
return $command->run($input, $output);
} catch (\Throwable $e) {
fwrite(STDERR, "Error: {$e->getMessage()}\n");
return 1;
}
}
private function listCommands(): void
{
echo "{$this->name} {$this->version}\n\nAvailable commands:\n";
$grouped = [];
foreach ($this->commands as $name => $command) {
$namespace = str_contains($name, ':') ? explode(':', $name)[0] : '';
$grouped[$namespace][$name] = $command->getDescription();
}
ksort($grouped);
foreach ($grouped as $ns => $commands) {
if ($ns) echo "\n {$ns}\n";
foreach ($commands as $name => $desc) {
printf(" %-30s %s\n", $name, $desc);
}
}
}
private function registerBuiltInCommands(): void
{
// Built-in framework commands registered here
// $this->add(new ListCommand($this));
// $this->add(new HelpCommand($this));
}
}