CLI & Console API

Command-line interface, console commands, argument parsing, and interactive prompts. The CLI system provides a powerful foundation for building command-line tools, background tasks, and interactive console applications.

Console

Main console application class that manages commands, handles input/output, and provides a fluent interface for building CLI applications. The Console class serves as the entry point for all command-line operations.

Console Application Setup

Basic Console Application

Create and configure a console application.

use Forge\Console\Console;
use Forge\Console\Input\Input;
use Forge\Console\Output\Output;

// Create console application
$console = new Console('Forge Engine CLI', '1.0.0');

// Set application description
$console->setDescription('Command-line interface for Forge Engine applications');

// Configure global options
$console->setGlobalOptions([
    ['help', 'h', InputOption::VALUE_NONE, 'Display help for the given command'],
    ['quiet', 'q', InputOption::VALUE_NONE, 'Do not output any message'],
    ['verbose', 'v', InputOption::VALUE_NONE, 'Increase the verbosity of messages'],
    ['version', 'V', InputOption::VALUE_NONE, 'Display this application version'],
    ['ansi', '', InputOption::VALUE_NONE, 'Force ANSI output'],
    ['no-ansi', '', InputOption::VALUE_NONE, 'Disable ANSI output'],
    ['no-interaction', 'n', InputOption::VALUE_NONE, 'Do not ask any interactive question']
]);

// Set default command
$console->setDefaultCommand('list');

// Run the application
$console->run();

Adding Commands

Register commands with the console application.

// Add a single command
$console->add(new GreetCommand());

// Add multiple commands
$console->addCommands([
    new GreetCommand(),
    new DatabaseMigrateCommand(),
    new CacheClearCommand(),
    new ServeCommand()
]);

// Add command using class name
$console->addCommand('app:serve', ServeCommand::class);

// Add command with configuration
$console->addCommand('cache:clear', [
    'class' => CacheClearCommand::class,
    'description' => 'Clear the application cache',
    'help' => 'This command clears all cached data from the application'
]);

// Add anonymous command
$console->addCommand('test:connection', function(Input $input, Output $output) {
    $output->writeln('Testing database connection...');
    
    try {
        $db = Container::getInstance()->make('db');
        $db->ping();
        $output->writeln('Database connection successful!');
        return 0;
    } catch (\Exception $e) {
        $output->writeln('Database connection failed: ' . $e->getMessage() . '');
        return 1;
    }
});

Console Configuration

setAutoExit(bool $autoExit): void

Set whether to automatically exit after running a command.

// Don't auto-exit (useful for testing)
$console->setAutoExit(false);

// Run command and capture output
$exitCode = $console->run($input, $output);

// Process results
if ($exitCode === 0) {
    echo "Command executed successfully\n";
} else {
    echo "Command failed with exit code: {$exitCode}\n";
}

// Auto-exit (default behavior)
$console->setAutoExit(true);
$console->run(); // Will exit after completion

setCatchExceptions(bool $catch): void

Set whether to catch exceptions during command execution.

// Catch and display exceptions nicely
$console->setCatchExceptions(true);

// Don't catch exceptions (for debugging)
$console->setCatchExceptions(false);

// Custom exception handling
$console->setExceptionHandler(function(\Exception $e, Output $output) {
    $output->writeln('An error occurred:');
    $output->writeln('' . $e->getMessage() . '');
    $output->writeln('');
    $output->writeln('Stack trace:');
    $output->writeln($e->getTraceAsString());
    
    return 1;
});

Command

Base class for all console commands providing argument parsing, input validation, and execution logic. Commands are the building blocks of CLI applications, each handling a specific task or operation.

Creating Commands

Basic Command Structure

Create a custom command by extending the base Command class.

namespace App\Console\Commands;

use Forge\Console\Command;
use Forge\Console\Input\InputArgument;
use Forge\Console\Input\InputOption;
use Forge\Console\Input\InputInterface;
use Forge\Console\Output\OutputInterface;

class GreetCommand extends Command
{
    protected static $defaultName = 'app:greet';
    protected static $defaultDescription = 'Greet a user';
    
    protected function configure(): void
    {
        $this
            ->setName('app:greet')
            ->setDescription('Greet a user')
            ->setHelp('This command greets a user with a customizable message')
            ->addArgument(
                'name',
                InputArgument::REQUIRED,
                'The name of the user to greet'
            )
            ->addOption(
                'greeting',
                'g',
                InputOption::VALUE_OPTIONAL,
                'Custom greeting message',
                'Hello'
            )
            ->addOption(
                'uppercase',
                'u',
                InputOption::VALUE_NONE,
                'Convert greeting to uppercase'
            );
    }
    
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $name = $input->getArgument('name');
        $greeting = $input->getOption('greeting');
        $uppercase = $input->getOption('uppercase');
        
        $message = "{$greeting}, {$name}!";
        
        if ($uppercase) {
            $message = strtoupper($message);
        }
        
        $output->writeln('' . $message . '');
        
        return Command::SUCCESS;
    }
}

Command Arguments

Define and use command arguments.

class DatabaseBackupCommand extends Command
{
    protected function configure(): void
    {
        $this
            ->setName('db:backup')
            ->setDescription('Backup the database')
            ->addArgument(
                'database',
                InputArgument::OPTIONAL,
                'Database name to backup',
                'default'
            )
            ->addArgument(
                'filename',
                InputArgument::OPTIONAL,
                'Backup filename',
                null
            )
            ->addArgument(
                'tables',
                InputArgument::IS_ARRAY | InputArgument::OPTIONAL,
                'Specific tables to backup'
            );
    }
    
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $database = $input->getArgument('database');
        $filename = $input->getArgument('filename');
        $tables = $input->getArgument('tables');
        
        // Generate filename if not provided
        if (!$filename) {
            $filename = sprintf(
                '%s_%s.sql',
                $database,
                date('Y-m-d_H-i-s')
            );
        }
        
        $output->writeln("Backing up database: {$database}");
        
        if (!empty($tables)) {
            $output->writeln("Tables: " . implode(', ', $tables) . "");
        }
        
        $output->writeln("Backup file: {$filename}");
        
        // Perform backup logic here
        
        return Command::SUCCESS;
    }
}

Command Options

Input Options

Define and use command options.

class UserCreateCommand extends Command
{
    protected function configure(): void
    {
        $this
            ->setName('user:create')
            ->setDescription('Create a new user')
            ->addArgument('email', InputArgument::REQUIRED, 'User email address')
            ->addArgument('password', InputArgument::OPTIONAL, 'User password')
            ->addOption('name', null, InputOption::VALUE_REQUIRED, 'User full name')
            ->addOption('role', null, InputOption::VALUE_REQUIRED, 'User role', 'user')
            ->addOption('verified', null, InputOption::VALUE_NONE, 'Mark email as verified')
            ->addOption('send-email', null, InputOption::VALUE_NONE, 'Send welcome email')
            ->addOption('force', 'f', InputOption::VALUE_NONE, 'Force creation if user exists');
    }
    
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $email = $input->getArgument('email');
        $password = $input->getArgument('password');
        
        // Generate password if not provided
        if (!$password) {
            $password = $this->generatePassword();
            $output->writeln("Generated password: {$password}");
        }
        
        $name = $input->getOption('name');
        $role = $input->getOption('role');
        $verified = $input->getOption('verified');
        $sendEmail = $input->getOption('send-email');
        $force = $input->getOption('force');
        
        // Check if user exists
        if ($this->userExists($email) && !$force) {
            $output->writeln("User with email {$email} already exists!");
            $output->writeln("Use --force to override");
            return Command::FAILURE;
        }
        
        // Create user
        $user = $this->createUser([
            'email' => $email,
            'password' => $password,
            'name' => $name,
            'role' => $role,
            'verified' => $verified
        ]);
        
        $output->writeln("User created successfully!");
        $output->writeln("Email: {$user->email}");
        $output->writeln("Role: {$user->role}");
        
        if ($sendEmail) {
            $this->sendWelcomeEmail($user);
            $output->writeln("Welcome email sent!");
        }
        
        return Command::SUCCESS;
    }
    
    private function generatePassword(): string
    {
        return bin2hex(random_bytes(8));
    }
}

Interactive Commands

User Interaction

Create interactive commands with user prompts.

use Forge\Console\Style\SymfonyStyle;
use Forge\Console\Question\ConfirmationQuestion;
use Forge\Console\Question\Question;
use Forge\Console\Question\ChoiceQuestion;

class SetupCommand extends Command
{
    protected function configure(): void
    {
        $this
            ->setName('app:setup')
            ->setDescription('Interactive application setup');
    }
    
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = new SymfonyStyle($input, $output);
        
        $io->title('Application Setup');
        $io->text('This wizard will help you configure your application.');
        $io->newLine();
        
        // Ask for application name
        $appName = $io->ask('Application name', 'My Application');
        
        // Ask for environment
        $environment = $io->choice(
            'Select environment',
            ['development', 'staging', 'production'],
            'development'
        );
        
        // Ask for database configuration
        $io->section('Database Configuration');
        
        $dbHost = $io->ask('Database host', 'localhost');
        $dbPort = $io->ask('Database port', '3306');
        $dbName = $io->ask('Database name', 'forge_app');
        $dbUser = $io->ask('Database username', 'root');
        $dbPass = $io->askHidden('Database password');
        
        // Confirm configuration
        $io->section('Configuration Summary');
        $io->table(
            ['Setting', 'Value'],
            [
                ['Application Name', $appName],
                ['Environment', $environment],
                ['Database Host', $dbHost],
                ['Database Port', $dbPort],
                ['Database Name', $dbName],
                ['Database User', $dbUser],
                ['Database Password', str_repeat('*', strlen($dbPass))]
            ]
        );
        
        if (!$io->confirm('Do you want to proceed with this configuration?', true)) {
            $io->warning('Setup cancelled.');
            return Command::FAILURE;
        }
        
        // Perform setup
        $io->section('Setting up application...');
        
        $progressBar = $io->createProgressBar(100);
        $progressBar->start();
        
        // Step 1: Create configuration files
        $this->createConfigFiles([
            'app_name' => $appName,
            'environment' => $environment,
            'database' => [
                'host' => $dbHost,
                'port' => $dbPort,
                'name' => $dbName,
                'user' => $dbUser,
                'password' => $dbPass
            ]
        ]);
        
        $progressBar->advance(25);
        
        // Step 2: Run migrations
        $this->runMigrations();
        $progressBar->advance(25);
        
        // Step 3: Seed database
        $this->seedDatabase();
        $progressBar->advance(25);
        
        // Step 4: Clear caches
        $this->clearCaches();
        $progressBar->advance(25);
        
        $progressBar->finish();
        $io->newLine(2);
        
        $io->success('Application setup completed successfully!');
        
        return Command::SUCCESS;
    }
}

Input & Output

Handle user input and console output with formatting, colors, and interactive elements. The Input and Output classes provide a rich interface for reading user input and displaying formatted output.

Input Handling

Input Arguments and Options

Access command arguments and options.

class TestCommand extends Command
{
    protected function configure(): void
    {
        $this
            ->setName('test:input')
            ->setDescription('Test input handling')
            ->addArgument('name', InputArgument::REQUIRED, 'Your name')
            ->addArgument('emails', InputArgument::IS_ARRAY, 'Email addresses')
            ->addOption('verbose', 'v', InputOption::VALUE_NONE, 'Verbose output')
            ->addOption('format', 'f', InputOption::VALUE_REQUIRED, 'Output format', 'text')
            ->addOption('count', 'c', InputOption::VALUE_OPTIONAL, 'Number of iterations', 1);
    }
    
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        // Get arguments
        $name = $input->getArgument('name');
        $emails = $input->getArgument('emails');
        
        // Get options
        $verbose = $input->getOption('verbose');
        $format = $input->getOption('format');
        $count = $input->getOption('count');
        
        // Check if option is set
        if ($input->hasOption('verbose')) {
            $output->writeln("Verbose mode enabled");
        }
        
        // Get all arguments
        $arguments = $input->getArguments();
        foreach ($arguments as $name => $value) {
            if (is_array($value)) {
                $output->writeln("{$name}: " . implode(', ', $value));
            } else {
                $output->writeln("{$name}: {$value}");
            }
        }
        
        // Get all options
        $options = $input->getOptions();
        foreach ($options as $name => $value) {
            if ($value !== false && $value !== null) {
                $output->writeln("Option {$name}: {$value}");
            }
        }
        
        // Interactive input
        if ($input->isInteractive()) {
            $helper = $this->getHelper('question');
            
            $question = new Question('Please enter your age: ');
            $age = $helper->ask($input, $output, $question);
            
            $output->writeln("Your age is: {$age}");
        }
        
        return Command::SUCCESS;
    }
}

Output Formatting

Styled Output

Format console output with colors and styles.

class StyledOutputCommand extends Command
{
    protected function configure(): void
    {
        $this->setName('demo:styles')->setDescription('Demonstrate output styles');
    }
    
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        // Basic styles
        $output->writeln('This is info text (green)');
        $output->writeln('This is comment text (yellow)');
        $output->writeln('This is question text (cyan)');
        $output->writeln('This is error text (red)');
        
        // Custom colors
        $output->writeln('This is red text');
        $output->writeln('This is green text');
        $output->writeln('This is blue text');
        $output->writeln('This is magenta text');
        
        // Background colors
        $output->writeln('White text on red background');
        $output->writeln('Black text on green background');
        $output->writeln('White text on blue background');
        
        // Text formatting
        $output->writeln('Bold text');
        $output->writeln('Underlined text');
        $output->writeln('Blinking text');
        $output->writeln('Reversed text');
        
        // Combined styles
        $output->writeln('Bold red text');
        $output->writeln('Underlined white on blue');
        
        // Multiple lines
        $output->writeln([
            'Line 1',
            'Line 2',
            'Line 3'
        ]);
        
        // Without newlines
        $output->write('Processing... ');
        sleep(1);
        $output->writeln('Done!');
        
        // Verbose output
        if ($output->isVerbose()) {
            $output->writeln('Verbose information');
        }
        
        if ($output->isVeryVerbose()) {
            $output->writeln('Very verbose information');
        }
        
        if ($output->isDebug()) {
            $output->writeln('Debug information');
        }
        
        return Command::SUCCESS;
    }
}

Progress Bars

Display progress bars for long-running operations.

class ProgressCommand extends Command
{
    protected function configure(): void
    {
        $this->setName('demo:progress')->setDescription('Demonstrate progress bars');
    }
    
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        // Create progress bar
        $progressBar = new ProgressBar($output, 100);
        
        // Customize progress bar
        $progressBar->setFormat('debug');
        $progressBar->setBarCharacter('');
        $progressBar->setEmptyBarCharacter('░');
        $progressBar->setProgressCharacter('➤');
        $progressBar->setBarWidth(50);
        
        // Start progress bar
        $progressBar->start();
        
        // Simulate work
        for ($i = 0; $i < 100; $i++) {
            usleep(20000); // 20ms
            $progressBar->advance();
        }
        
        // Finish progress bar
        $progressBar->finish();
        $output->writeln('');
        
        // Custom progress bar format
        $progressBar = new ProgressBar($output, 50);
        $progressBar->setFormat('Processing: %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%');
        
        $progressBar->start();
        for ($i = 0; $i < 50; $i++) {
            usleep(40000);
            $progressBar->advance();
        }
        $progressBar->finish();
        $output->writeln('');
        
        // Progress bar with messages
        $progressBar = new ProgressBar($output, 10);
        $progressBar->setFormat("%message%\n%current%/%max% [%bar%] %percent:3s%%");
        
        $progressBar->start();
        for ($i = 0; $i < 10; $i++) {
            $progressBar->setMessage("Processing item {$i}");
            $progressBar->advance();
            usleep(100000);
        }
        $progressBar->finish();
        
        return Command::SUCCESS;
    }
}

Tables and Lists

Display data in formatted tables and lists. The Console component provides helpers for creating nicely formatted tables and lists from your data.

Table Display

Creating Tables

Display data in formatted tables.

use Forge\Console\Helper\Table;

class TableCommand extends Command
{
    protected function configure(): void
    {
        $this->setName('demo:table')->setDescription('Demonstrate table display');
    }
    
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        // Simple table
        $table = new Table($output);
        $table->setHeaders(['ID', 'Name', 'Email', 'Status']);
        $table->setRows([
            [1, 'John Doe', 'john@example.com', 'Active'],
            [2, 'Jane Smith', 'jane@example.com', 'Active'],
            [3, 'Bob Johnson', 'bob@example.com', 'Pending'],
            [4, 'Alice Brown', 'alice@example.com', 'Inactive']
        ]);
        $table->render();
        
        // Table with style
        $table = new Table($output);
        $table->setStyle('borderless');
        $table->setHeaders(['Feature', 'Status', 'Description']);
        $table->setRows([
            ['Database', '', 'MySQL 8.0 configured'],
            ['Cache', '', 'Redis cache enabled'],
            ['Queue', '⚠', 'Queue not configured'],
            ['Mail', '', 'Mail driver not set']
        ]);
        $table->render();
        
        // Complex table with alignment
        $table = new Table($output);
        $table->setHeaders(['Module', 'Version', 'Status', 'Dependencies']);
        $table->setRows([
            ['Auth', '1.2.0', 'Enabled', 'Database, Cache'],
            ['Blog', '2.1.0', 'Enabled', 'Auth, Media'],
            ['Media', '1.0.0', 'Disabled', 'Storage'],
            ['Analytics', '1.5.0', 'Enabled', 'Database']
        ]);
        
        // Set column styles
        $table->setColumnStyle(0, ['align' => 'left']);
        $table->setColumnStyle(1, ['align' => 'center']);
        $table->setColumnStyle(2, ['align' => 'center']);
        $table->setColumnStyle(3, ['align' => 'left']);
        
        $table->render();
        
        // Table from data source
        $users = $this->getUsers(); // Assume this returns user data
        
        $table = new Table($output);
        $table->setHeaders(['ID', 'Name', 'Email', 'Created', 'Last Login']);
        
        foreach ($users as $user) {
            $table->addRow([
                $user->id,
                $user->name,
                $user->email,
                $user->created_at->format('Y-m-d'),
                $user->last_login ? $user->last_login->diffForHumans() : 'Never'
            ]);
        }
        
        $table->render();
        
        return Command::SUCCESS;
    }
}

List Display

Creating Lists

Display data in formatted lists.

use Forge\Console\Helper\DescriptorHelper;

class ListCommand extends Command
{
    protected function configure(): void
    {
        $this->setName('demo:list')->setDescription('Demonstrate list display');
    }
    
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = new SymfonyStyle($input, $output);
        
        // Simple list
        $io->listing([
            'Install dependencies',
            'Configure environment',
            'Run migrations',
            'Seed database',
            'Start application'
        ]);
        
        // Definition list
        $io->definitionList(
            ['Application' => 'Forge Engine'],
            ['Version' => '1.0.0'],
            ['Environment' => 'production'],
            ['Debug Mode' => 'disabled'],
            ['Cache Driver' => 'redis'],
            ['Database' => 'MySQL 8.0']
        );
        
        // Section with items
        $io->section('Available Commands');
        $io->listing([
            'cache:clear - Clear application cache',
            'db:migrate - Run database migrations',
            'user:create - Create a new user',
            'serve - Start development server'
        ]);
        
        // Nested sections
        $io->section('System Status');
        $io->text('Checking system requirements...');
        
        $io->section('PHP Extensions');
        $io->listing([
            ' PDO',
            ' Mbstring',
            ' OpenSSL',
            ' Redis (optional)'
        ]);
        
        $io->section('Directory Permissions');
        $io->listing([
            ' storage/ - Writable',
            ' bootstrap/cache/ - Writable',
            ' public/uploads/ - Writable'
        ]);
        
        // Custom list with icons
        $io->section('Module Status');
        $modules = [
            'Auth' => '✓ Enabled',
            'Blog' => '✓ Enabled',
            'Media' => '⚠ Disabled',
            'Analytics' => '✓ Enabled'
        ];
        
        foreach ($modules as $name => $status) {
            $io->writeln("  {$name}: {$status}");
        }
        
        return Command::SUCCESS;
    }
}

Usage Examples

Common patterns and examples for working with the CLI & Console system.

Database Management Commands

// Database migration command
class MigrateCommand extends Command
{
    protected function configure(): void
    {
        $this
            ->setName('db:migrate')
            ->setDescription('Run database migrations')
            ->addOption('force', 'f', InputOption::VALUE_NONE, 'Force migration in production')
            ->addOption('path', null, InputOption::VALUE_REQUIRED, 'Path to migration files')
            ->addOption('seed', null, InputOption::VALUE_NONE, 'Seed database after migration')
            ->addOption('fresh', null, InputOption::VALUE_NONE, 'Drop all tables and re-migrate');
    }
    
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = new SymfonyStyle($input, $output);
        
        $force = $input->getOption('force');
        $path = $input->getOption('path') ?? 'database/migrations';
        $seed = $input->getOption('seed');
        $fresh = $input->getOption('fresh');
        
        // Check environment
        if (Container::getInstance()->make('app')->environment('production') && !$force) {
            $io->error('Migration in production requires --force option');
            return Command::FAILURE;
        }
        
        $io->title('Database Migration');
        
        if ($fresh) {
            $io->warning('This will drop all tables and re-create them');
            if (!$io->confirm('Are you sure you want to continue?', false)) {
                return Command::FAILURE;
            }
            
            $io->text('Dropping all tables...');
            $this->dropAllTables();
        }
        
        // Get migration files
        $migrations = $this->getMigrationFiles($path);
        
        if (empty($migrations)) {
            $io->info('No migrations found');
            return Command::SUCCESS;
        }
        
        $io->text(sprintf('Found %d migration(s)', count($migrations)));
        
        $progressBar = $io->createProgressBar(count($migrations));
        $progressBar->start();
        
        $executed = 0;
        foreach ($migrations as $migration) {
            try {
                $this->runMigration($migration);
                $executed++;
                $progressBar->advance();
            } catch (\Exception $e) {
                $progressBar->clear();
                $io->error("Migration failed: " . $e->getMessage());
                return Command::FAILURE;
            }
        }
        
        $progressBar->finish();
        $io->newLine();
        
        $io->success(sprintf('Executed %d migration(s)', $executed));
        
        if ($seed) {
            $io->section('Seeding Database');
            $this->seedDatabase();
            $io->success('Database seeded successfully');
        }
        
        return Command::SUCCESS;
    }
}

// Database backup command
class BackupCommand extends Command
{
    protected function configure(): void
    {
        $this
            ->setName('db:backup')
            ->setDescription('Backup the database')
            ->addArgument('filename', InputArgument::OPTIONAL, 'Backup filename')
            ->addOption('compress', 'c', InputOption::VALUE_NONE, 'Compress backup file')
            ->addOption('tables', 't', InputOption::VALUE_REQUIRED, 'Specific tables to backup')
            ->addOption('exclude', 'e', InputOption::VALUE_REQUIRED, 'Tables to exclude from backup');
    }
    
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = new SymfonyStyle($input, $output);
        
        $filename = $input->getArgument('filename');
        $compress = $input->getOption('compress');
        $tables = $input->getOption('tables');
        $exclude = $input->getOption('exclude');
        
        // Generate filename if not provided
        if (!$filename) {
            $filename = sprintf('backup_%s.sql', date('Y-m-d_H-i-s'));
        }
        
        $io->title('Database Backup');
        
        // Parse table lists
        $includeTables = $tables ? explode(',', $tables) : [];
        $excludeTables = $exclude ? explode(',', $exclude) : [];
        
        // Get database configuration
        $config = config('database');
        $io->text(sprintf('Backing up database: %s', $config['database']));
        
        if (!empty($includeTables)) {
            $io->text(sprintf('Including tables: %s', implode(', ', $includeTables)));
        }
        
        if (!empty($excludeTables)) {
            $io->text(sprintf('Excluding tables: %s', implode(', ', $excludeTables)));
        }
        
        // Perform backup
        try {
            $backupPath = $this->performBackup($filename, $includeTables, $excludeTables);
            
            if ($compress) {
                $io->text('Compressing backup...');
                $backupPath = $this->compressFile($backupPath);
            }
            
            $fileSize = $this->formatBytes(filesize($backupPath));
            $io->success(sprintf('Backup completed: %s (%s)', basename($backupPath), $fileSize));
            
        } catch (\Exception $e) {
            $io->error('Backup failed: ' . $e->getMessage());
            return Command::FAILURE;
        }
        
        return Command::SUCCESS;
    }
    
    private function formatBytes(int $bytes): string
    {
        $units = ['B', 'KB', 'MB', 'GB', 'TB'];
        $i = 0;
        
        while ($bytes >= 1024 && $i < count($units) - 1) {
            $bytes /= 1024;
            $i++;
        }
        
        return round($bytes, 2) . ' ' . $units[$i];
    }
}

Application Maintenance Commands

// Maintenance mode command
class MaintenanceCommand extends Command
{
    protected function configure(): void
    {
        $this
            ->setName('down')
            ->setDescription('Put the application into maintenance mode')
            ->addOption('message', 'm', InputOption::VALUE_REQUIRED, 'Maintenance message')
            ->addOption('retry', 'r', InputOption::VALUE_REQUIRED, 'Retry after seconds', 60)
            ->addOption('allow', null, InputOption::VALUE_REQUIRED, 'Allowed IP addresses');
        
        $this->addSubcommand('up', new UpCommand());
    }
    
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = new SymfonyStyle($input, $output);
        
        $message = $input->getOption('message') ?? 'Application is under maintenance';
        $retry = $input->getOption('retry');
        $allow = $input->getOption('allow');
        
        $io->title('Maintenance Mode');
        
        // Create maintenance file
        $maintenanceData = [
            'time' => time(),
            'message' => $message,
            'retry' => $retry,
            'allowed' => $allow ? explode(',', $allow) : []
        ];
        
        file_put_contents(
            storage_path('framework/maintenance.php'),
            'warning('Application is now in maintenance mode');
        $io->text("Message: {$message}");
        $io->text("Retry after: {$retry} seconds");
        
        if ($allow) {
            $io->text("Allowed IPs: {$allow}");
        }
        
        return Command::SUCCESS;
    }
}

class UpCommand extends Command
{
    protected function configure(): void
    {
        $this
            ->setName('up')
            ->setDescription('Bring the application out of maintenance mode');
    }
    
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = new SymfonyStyle($input, $output);
        
        $maintenanceFile = storage_path('framework/maintenance.php');
        
        if (!file_exists($maintenanceFile)) {
            $io->info('Application is not in maintenance mode');
            return Command::SUCCESS;
        }
        
        unlink($maintenanceFile);
        
        $io->success('Application is now live!');
        
        return Command::SUCCESS;
    }
}

// Cache management command
class CacheCommand extends Command
{
    protected function configure(): void
    {
        $this
            ->setName('cache:clear')
            ->setDescription('Clear application cache')
            ->addOption('tags', 't', InputOption::VALUE_REQUIRED, 'Specific cache tags to clear')
            ->addOption('stores', 's', InputOption::VALUE_REQUIRED, 'Specific cache stores to clear');
    }
    
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = new SymfonyStyle($input, $output);
        
        $tags = $input->getOption('tags');
        $stores = $input->getOption('stores');
        
        $io->title('Clearing Application Cache');
        
        $storesToClear = $stores ? explode(',', $stores) : ['default'];
        $tagsToClear = $tags ? explode(',', $tags) : [];
        
        $progressBar = $io->createProgressBar(count($storesToClear));
        $progressBar->start();
        
        foreach ($storesToClear as $store) {
            try {
                if (!empty($tagsToClear)) {
                    // Clear specific tags
                    foreach ($tagsToClear as $tag) {
                        cache()->store($store)->tags([$tag])->flush();
                    }
                } else {
                    // Clear entire store
                    cache()->store($store)->flush();
                }
                
                $progressBar->advance();
            } catch (\Exception $e) {
                $progressBar->clear();
                $io->error("Failed to clear cache store {$store}: " . $e->getMessage());
                return Command::FAILURE;
            }
        }
        
        $progressBar->finish();
        $io->newLine();
        
        $io->success('Cache cleared successfully!');
        
        return Command::SUCCESS;
    }
}

Best Practices

Recommended patterns and guidelines for working with the CLI & Console system.

Do's

  • • Use descriptive command names
  • • Provide helpful descriptions and help text
  • • Use appropriate exit codes
  • • Handle errors gracefully
  • • Use progress bars for long operations
  • • Provide verbose output options
  • • Use colors and formatting appropriately
  • • Validate input arguments and options
  • • Use confirmation prompts for destructive operations
  • • Provide meaningful error messages

Don'ts

  • • Don't use generic command names
  • • Don't ignore user input validation
  • • Don't use hardcoded values
  • • Don't ignore exit codes
  • • Don't overuse colors and formatting
  • • Don't assume interactive mode
  • • Don't ignore exceptions
  • • Don't use excessive output
  • • Don't ignore command dependencies
  • • Don't skip error handling