0

Console application bootstrap — argv parsing

Intermediate5 min read·fw-10-001

Concept

Console application bootstrap is the entry point for CLI commands. When you run php artisan list, the framework bootstraps the console application, discovers available commands, and dispatches to the correct command.

argv parsing: PHP provides $_SERVER['argv'] (array of command-line arguments) and $_SERVER['argc'] (count). argv[0] is the script name (artisan). argv[1] is the command name (list, migrate, serve). Subsequent elements are arguments and options.

Symfony Console: Laravel uses Symfony's Console component. For a custom framework, you can implement a simpler version. Key concepts:

  • Application: The root object. Registers commands and dispatches.
  • Command: Base class for individual commands.
  • Input: Parsed argv — methods to get arguments/options.
  • Output: Writes to stdout/stderr.

Bootstrap steps:

  1. Load autoloader and environment.
  2. Boot the application container (for service resolution).
  3. Create the ConsoleApplication.
  4. Register all commands.
  5. Run the application with $argv.

artisan file: The entry point for Laravel's console. A PHP script that bootstraps the app and calls $kernel->handle(new ArgvInput(), new ConsoleOutput()).

Auto-discovery: Commands in app/Console/Commands/ are discovered via filesystem scanning or class attributes.

Code Example

php
<?php
#!/usr/bin/env php
// artisan (entry point — no .php extension, executable via chmod +x)

define('LARAVEL_START', microtime(true));

require __DIR__ . '/vendor/autoload.php';

$app = require_once __DIR__ . '/bootstrap/app.php';

// Framework-specific: boot the console kernel
$kernel = $app->make(\Framework\Console\Kernel::class);

// Run and exit with the command's exit code
exit($kernel->handle());

---

// bootstrap/app.php — simplified
namespace Framework\Console;

class Kernel
{
    private Application $console;

    public function __construct(private readonly \Framework\Container\Container $container) {}

    public function handle(): int
    {
        $this->bootstrap();
        return $this->console->run();
    }

    private function bootstrap(): void
    {
        $this->console = new Application($this->container, 'My Framework', '1.0.0');
        // Register commands
        foreach ($this->discoverCommands() as $commandClass) {
            $this->console->add($this->container->make($commandClass));
        }
    }

    private function discoverCommands(): array
    {
        // Scan app/Console/Commands/ for command classes
        $commands = [];
        $dir = base_path('app/Console/Commands');
        if (!is_dir($dir)) return [];

        $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($dir));
        foreach ($iterator as $file) {
            if ($file->isFile() && $file->getExtension() === 'php') {
                $class = $this->fileToClassName($file->getPathname());
                if (class_exists($class) && is_subclass_of($class, Command::class)) {
                    $commands[] = $class;
                }
            }
        }
        return $commands;
    }

    private function fileToClassName(string $path): string
    {
        // Convert file path to PSR-4 class name
        $relative = str_replace(base_path() . '/', '', $path);
        return 'App\\' . str_replace(['/', '.php'], ['\\', ''], $relative);
    }
}