Cookbook

Writing Console Commands

Build your own Glueful CLI commands on Symfony Console — the BaseCommand base class, DI-enabled services, registration, and authoring best practices.

This guide covers how to author your own console commands in Glueful, built on Symfony Console with dependency-injection integration.

Overview

Glueful's console system provides a powerful command-line interface for application management, built on Symfony Console with dependency injection integration and enhanced features for modern development workflows.

Key Features

  • Symfony Console Integration: Modern CLI framework with advanced features
  • Dependency Injection: Full DI container integration for all commands
  • Enhanced Styling: SymfonyStyle integration with custom styling helpers
  • Interactive Commands: Rich user interaction with confirmations, choices, and progress bars
  • Production Safety: Built-in production environment safeguards
  • Extensible Architecture: Easy custom command creation with base classes
  • Multi-format Output: Support for table, JSON, and compact output formats

Console Architecture

The console system is built around several key components:

  1. Application: Central console application managing all commands
  2. BaseCommand: Enhanced base class with DI integration and styling
  3. Command Registry: Automatic command discovery and registration
  4. Service Integration: Full access to application services and utilities

Console Architecture

Application Class

use Glueful\Console\Application;

$container = container();
$app = new Application($container);
$app->run();

Key Features:

  • Automatic command registration via DI container
  • Enhanced error handling and exception management
  • Consistent branding and help system
  • Command categorization and organization

BaseCommand Class

All Glueful commands extend the enhanced BaseCommand:

use Glueful\Console\BaseCommand;

class MyCommand extends BaseCommand
{
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $this->info('Starting operation...');
        $this->success('Operation completed!');
        return self::SUCCESS;
    }
}

Enhanced Methods:

Output Methods:

  • $this->success($message) - Green success message with icon
  • $this->error($message) - Red error message with icon
  • $this->warning($message) - Yellow warning message with icon
  • $this->info($message) - Blue info message
  • $this->note($message) - Highlighted note/tip
  • $this->tip($message) - Legacy tip method (adds "Tip:" prefix)
  • $this->line($message) - Plain text line

Interactive Methods:

  • $this->confirm($question, $default) - Yes/no confirmation
  • $this->ask($question, $default) - Text input with optional default
  • $this->secret($question) - Hidden input for passwords
  • $this->choice($question, $choices, $default) - Multiple choice selection

Display Methods:

  • $this->table($headers, $rows) - Formatted table display
  • $this->createProgressBar($max) - Create progress bar instance
  • $this->progressBar($steps, $callback) - Progress bar with callback

Utility Methods:

  • $this->getService($serviceId) - Resolve service from DI container
  • $this->getServiceDynamic($serviceId) - Dynamic service resolution
  • $this->getContainer() - Access DI container
  • $this->isProduction() - Check if running in production
  • $this->confirmProduction($operation) - Force production confirmation

Custom Commands

Creating Custom Commands

<?php

namespace App\Console\Commands;

use Glueful\Console\BaseCommand;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

#[AsCommand(
    name: 'my:command',
    description: 'Custom command description'
)]
class MyCommand extends BaseCommand
{
    protected function configure(): void
    {
        $this->setDescription('Custom command description')
             ->setHelp('Detailed help text for the command')
             ->addArgument(
                 'name',
                 InputArgument::REQUIRED,
                 'Name argument'
             )
             ->addOption(
                 'force',
                 'f',
                 InputOption::VALUE_NONE,
                 'Force execution'
             );
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $name = $input->getArgument('name');
        $force = $input->getOption('force');

        // Production safety check
        if (!$force && !$this->confirmProduction('execute custom operation')) {
            return self::FAILURE;
        }

        try {
            $this->info("Starting operation for: {$name}");

            // Access services via DI container
            $myService = $this->getService(MyService::class);
            
            // Progress bar example
            $this->progressBar(10, function ($progressBar) use ($myService) {
                for ($i = 0; $i < 10; $i++) {
                    $myService->doSomething();
                    $progressBar->advance();
                    sleep(1);
                }
            });

            $this->success('Operation completed successfully!');
            return self::SUCCESS;
        } catch (\Exception $e) {
            $this->error('Operation failed: ' . $e->getMessage());
            return self::FAILURE;
        }
    }
}

Registering Custom Commands

// In your service provider or bootstrap file
$app = new Glueful\Console\Application($container);
$app->addCommand(MyCommand::class);

Command Templates

Use a concrete scaffold command instead of a generic generate:command helper:

# Create domain-specific classes with the scaffold commands that exist today
php glueful scaffold:job SendDigestEmail
php glueful scaffold:middleware EnsureAdmin
php glueful scaffold:test UserControllerTest

Best Practices

Command Design

  1. Use Descriptive Names: Follow the group:action pattern
  2. Provide Good Help: Include detailed descriptions and examples
  3. Handle Errors Gracefully: Use try-catch blocks and meaningful error messages
  4. Production Safety: Always check production environment for destructive operations
  5. Progress Feedback: Use progress bars for long-running operations
Refresh the command manifest after upgrading to 1.52.0

Input Validation

protected function execute(InputInterface $input, OutputInterface $output): int
{
    // Validate required options
    $email = $input->getOption('email');
    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        $this->error('Invalid email address provided');
        return self::FAILURE;
    }

    // Validate file paths
    $file = $input->getArgument('file');
    if (!file_exists($file)) {
        $this->error("File not found: {$file}");
        return self::FAILURE;
    }

    return self::SUCCESS;
}

Interactive Commands

protected function execute(InputInterface $input, OutputInterface $output): int
{
    // Confirmation prompts
    if (!$this->confirm('Do you want to continue?', false)) {
        $this->info('Operation cancelled');
        return self::SUCCESS;
    }

    // Choice menus
    $action = $this->choice(
        'What would you like to do?',
        ['create', 'update', 'delete'],
        'create'
    );

    // Text input with validation
    $name = $this->ask('Enter name');
    while (empty($name)) {
        $this->warning('Name cannot be empty');
        $name = $this->ask('Enter name');
    }

    return self::SUCCESS;
}

Output Formatting

protected function execute(InputInterface $input, OutputInterface $output): int
{
    // Table display
    $this->table(
        ['ID', 'Name', 'Status'],
        [
            [1, 'John Doe', 'Active'],
            [2, 'Jane Smith', 'Inactive']
        ]
    );

    // Progress tracking
    $items = range(1, 100);
    $this->progressBar(count($items), function ($progressBar) use ($items) {
        foreach ($items as $item) {
            // Process item
            sleep(0.1);
            $progressBar->advance();
        }
    });

    return self::SUCCESS;
}

Error Handling

protected function execute(InputInterface $input, OutputInterface $output): int
{
    try {
        // Risky operation
        $result = $this->performOperation();
        
        if (!$result) {
            $this->warning('Operation completed with warnings');
            return self::SUCCESS;
        }
        
        $this->success('Operation completed successfully');
        return self::SUCCESS;
    } catch (ValidationException $e) {
        $this->error('Validation failed: ' . $e->getMessage());
        return self::INVALID;
    } catch (\Exception $e) {
        $this->error('Unexpected error: ' . $e->getMessage());
        
        if ($input->getOption('verbose')) {
            $this->line($e->getTraceAsString());
        }
        
        return self::FAILURE;
    }
}

Service Integration

class MyCommand extends BaseCommand
{
    private MyService $myService;
    private DatabaseInterface $database;

    public function __construct()
    {
        parent::__construct();
        
        // Resolve services from DI container
        $this->myService = $this->getService(MyService::class);
        $this->database = $this->getService(DatabaseInterface::class);
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        // Use services
        $users = $this->database->select('users')->get();
        $result = $this->myService->processUsers($users);

        return self::SUCCESS;
    }
}

Configuration and Environment

protected function execute(InputInterface $input, OutputInterface $output): int
{
    // Check environment
    if ($this->isProduction() && !$input->getOption('force')) {
        if (!$this->confirmProduction('perform this operation')) {
            return self::FAILURE;
        }
    }

    // Access configuration
    $timeout = config($this->getContext(), 'app.timeout', 30);
    $debug = config($this->getContext(), 'app.debug', false);

    return self::SUCCESS;
}

This comprehensive console system provides powerful tools for application management, development workflows, and system administration while maintaining consistency, safety, and ease of use.