output = $output ?? new ConsoleOutput(); // Setup signal handlers für graceful shutdown $this->setupSignalHandlers(); // Setze den Fenstertitel $this->output->writeWindowTitle($this->title); try { $this->initializeCommandRegistry(); $this->initializeErrorHandler(); } catch (Throwable $e) { // Log the original error for debugging error_log("Console initialization failed: " . $e->getMessage()); error_log("Stack trace: " . $e->getTraceAsString()); throw FrameworkException::create( ErrorCode::SYS_INITIALIZATION_FAILED, 'Failed to initialize console application: ' . $e->getMessage() ); } } private function initializeErrorHandler(): void { // Create error recovery components $suggestionEngine = new CommandSuggestionEngine($this->commandRegistry->getCommandList()); $recoveryService = new ErrorRecoveryService( $suggestionEngine, $this->commandRegistry->getCommandList(), $this->commandRegistry ); // Get logger if available $logger = $this->container->has(Logger::class) ? $this->container->get(Logger::class) : null; $this->errorHandler = new ConsoleErrorHandler($recoveryService, $logger); } private function setupSignalHandlers(): void { if (function_exists('pcntl_signal')) { pcntl_signal(SIGTERM, [$this, 'handleShutdown']); pcntl_signal(SIGINT, [$this, 'handleShutdown']); pcntl_signal(SIGHUP, [$this, 'handleShutdown']); } } public function handleShutdown(int $signal): void { $this->shutdownRequested = true; $this->output->writeLine("Shutdown signal received ({$signal}). Cleaning up...", ConsoleColor::YELLOW); // Cleanup resources $this->cleanup(); exit(ExitCode::SUCCESS->value); } private function cleanup(): void { // Reset window title $this->output->writeWindowTitle('Terminal'); // No specific cleanup needed for CommandRegistry } private function initializeCommandRegistry(): void { $discoveryRegistry = $this->container->get(DiscoveryRegistry::class); $this->commandRegistry = new CommandRegistry($this->container, $discoveryRegistry); // Fallback: Force fresh discovery if no commands found if ($this->commandRegistry->getCommandList()->count() === 0) { if ($this->container->has(Logger::class)) { $logger = $this->container->get(Logger::class); $logger->warning('No commands found, forcing fresh discovery...', LogContext::withData([ 'component' => 'ConsoleApplication', ])); } // Force fresh discovery $bootstrapper = new \App\Framework\Discovery\DiscoveryServiceBootstrapper( $this->container, $this->container->get(\App\Framework\DateTime\Clock::class) ); $freshRegistry = $bootstrapper->performBootstrap( $this->container->get(\App\Framework\Core\PathProvider::class), $this->container->get(\App\Framework\Cache\Cache::class), null ); // Update container with fresh registry $this->container->instance(\App\Framework\Discovery\Results\DiscoveryRegistry::class, $freshRegistry); // Re-initialize command registry with fresh discovery $this->commandRegistry = new CommandRegistry($this->container, $freshRegistry); if ($this->container->has(Logger::class)) { $logger = $this->container->get(Logger::class); $commandCount = count($freshRegistry->attributes->get(\App\Framework\Console\ConsoleCommand::class)); $logger->info('Fresh discovery completed', LogContext::withData([ 'commands_found' => $commandCount, 'component' => 'ConsoleApplication', ])); } } } /** * Führt ein Kommando aus * @param array $argv */ public function run(array $argv): int { try { // Validate and sanitize input $argv = $this->validateAndSanitizeInput($argv); // Check for shutdown signal if (function_exists('pcntl_signal_dispatch')) { pcntl_signal_dispatch(); } if ($this->shutdownRequested) { return ExitCode::INTERRUPTED->value; } if (count($argv) < 2) { // Launch interactive TUI when no arguments provided return $this->launchInteractiveTUI(); } $commandName = $argv[1]; $arguments = array_slice($argv, 2); // Handle TUI launch flags if (in_array($commandName, ['--interactive', '--tui', '-i'])) { return $this->launchInteractiveTUI(); } // Handle built-in commands if (in_array($commandName, ['help', '--help', '-h'])) { // Spezifische Command-Hilfe if (! empty($arguments) && isset($arguments[0])) { $this->showCommandHelp($arguments[0]); } else { $this->showHelp(); } return ExitCode::SUCCESS->value; } $commandList = $this->commandRegistry->getCommandList(); // Prüfe ob es ein direktes Kommando ist if ($commandList->has($commandName)) { return $this->executeCommand($commandName, $arguments)->value; } // Prüfe ob es eine Kategorie ist $categories = $this->categorizeCommands($commandList); if (isset($categories[$commandName])) { $this->showCategoryCommands($commandName, $categories[$commandName]); return ExitCode::SUCCESS->value; } // Command/Kategorie nicht gefunden return $this->errorHandler->handleCommandNotFound($commandName, $this->output)->value; } catch (Throwable $e) { $this->output->writeError("Critical error: " . $e->getMessage()); $this->cleanup(); return ExitCode::GENERAL_ERROR->value; } } /** * @param array $argv * @return array */ private function validateAndSanitizeInput(array $argv): array { if (empty($argv)) { throw new \InvalidArgumentException('No arguments provided'); } // Validate argv array structure if (! is_array($argv) || ! isset($argv[0])) { throw new \InvalidArgumentException('Invalid argv structure'); } // Sanitize each argument return array_map(function ($arg) { if (! is_string($arg)) { throw new \InvalidArgumentException('All arguments must be strings'); } // Remove null bytes and control characters $sanitized = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', '', $arg); // Limit argument length to prevent memory issues if (strlen($sanitized) > 4096) { throw new \InvalidArgumentException('Argument too long (max 4096 characters)'); } return $sanitized; }, $argv); } /** * @param array $arguments */ private function executeCommand(string $commandName, array $arguments): ExitCode { try { // Check for shutdown signal before execution if (function_exists('pcntl_signal_dispatch')) { pcntl_signal_dispatch(); } if ($this->shutdownRequested) { return ExitCode::INTERRUPTED; } // Setze den Fenstertitel für das aktuelle Kommando $this->output->writeWindowTitle("{$this->scriptName} - {$commandName}"); // Execute command via registry $result = $this->commandRegistry->executeCommand($commandName, $arguments, $this->output); // Handle ConsoleResult (new) or ExitCode (legacy) return $this->processCommandResult($result); } catch (CommandNotFoundException $e) { return $this->errorHandler->handleCommandNotFound($commandName, $this->output); } catch (FrameworkException $e) { return $this->errorHandler->handleCommandExecutionError($commandName, $e, $this->output); } catch (\InvalidArgumentException $e) { return $this->errorHandler->handleValidationError($commandName, $e->getMessage(), $this->output); } catch (\RuntimeException $e) { return $this->errorHandler->handleCommandExecutionError($commandName, $e, $this->output); } catch (Throwable $e) { return $this->errorHandler->handleUnexpectedError($commandName, $e, $this->output); } finally { // Reset window title after command execution $this->output->writeWindowTitle($this->title); } } /** * Process command result - supports both ConsoleResult and ExitCode */ private function processCommandResult(mixed $result): ExitCode { // New ConsoleResult pattern if ($result instanceof \App\Framework\Console\Result\ConsoleResult) { // Render result to output $result->render($this->output); // Return exit code from result return $result->exitCode; } // Legacy ExitCode pattern if ($result instanceof ExitCode) { return $result; } // Legacy int pattern (for backwards compatibility) if (is_int($result)) { return ExitCode::from($result); } // Invalid return type - log warning and return error if ($this->container->has(Logger::class)) { $logger = $this->container->get(Logger::class); $logger->warning('Command returned invalid result type', LogContext::withData([ 'result_type' => get_debug_type($result), 'component' => 'ConsoleApplication', ])); } return ExitCode::GENERAL_ERROR; } private function showCommandUsage(string $commandName): void { try { $commandList = $this->commandRegistry->getCommandList(); if (! $commandList->has($commandName)) { return; } $command = $commandList->get($commandName); $this->output->writeLine("Usage:", ConsoleColor::BRIGHT_YELLOW); $this->output->writeLine(" php {$this->scriptName} {$commandName} [arguments]"); if (! empty($command->description)) { $this->output->newLine(); $this->output->writeLine("Description:", ConsoleColor::BRIGHT_YELLOW); $this->output->writeLine(" " . $command->description); } } catch (Throwable $e) { // Ignore errors in usage display } } private function showHelp(): void { $this->output->writeLine("Console Commands", ConsoleColor::BRIGHT_CYAN); $this->output->newLine(); $commandList = $this->commandRegistry->getCommandList(); if ($commandList->isEmpty()) { $this->output->writeLine(" Keine Kommandos verfügbar.", ConsoleColor::YELLOW); return; } // Kategorisiere Commands $categories = $this->categorizeCommands($commandList); // Zeige Kategorien-Übersicht $this->showCategoryOverview($categories); $this->output->newLine(); $this->output->writeLine("Verwendung:", ConsoleColor::BRIGHT_YELLOW); $this->output->writeLine(" php {$this->scriptName} # Interaktive TUI starten"); $this->output->writeLine(" php {$this->scriptName} --interactive # Interaktive TUI explizit starten"); $this->output->writeLine(" php {$this->scriptName} # Commands einer Kategorie anzeigen"); $this->output->writeLine(" php {$this->scriptName} [argumente] # Kommando direkt ausführen"); $this->output->writeLine(" php {$this->scriptName} help # Hilfe für spezifisches Kommando"); $this->output->newLine(); $this->output->writeLine("Hinweis:", ConsoleColor::CYAN); $this->output->writeLine(" Ohne Argumente wird automatisch die interaktive TUI gestartet."); $this->output->writeLine(" Die TUI bietet eine grafische Navigation durch alle verfügbaren Commands."); } /** * Kategorisiert Commands basierend auf ihrem Präfix */ private function categorizeCommands(CommandList $commandList): array { $categories = []; foreach ($commandList as $command) { $parts = explode(':', $command->name); $category = $parts[0]; if (! isset($categories[$category])) { $categories[$category] = []; } $categories[$category][] = $command; } // Sortiere Kategorien ksort($categories); return $categories; } /** * Zeigt eine übersichtliche Kategorien-Übersicht */ private function showCategoryOverview(array $categories): void { $this->output->writeLine("Verfügbare Kategorien:", ConsoleColor::BRIGHT_YELLOW); $this->output->newLine(); $categoryInfo = [ 'db' => 'Database operations (migrations, health checks)', 'errors' => 'Error management and analytics', 'backup' => 'Backup and restore operations', 'secrets' => 'Secret management and encryption', 'cache' => 'Cache management operations', 'demo' => 'Demo and example commands', 'logs' => 'Log management and rotation', 'alerts' => 'Alert system management', 'mcp' => 'Model Context Protocol server', 'make' => 'Code generation commands', 'docs' => 'Documentation generation', 'openapi' => 'OpenAPI specification generation', 'static' => 'Static file generation', 'redis' => 'Redis cache operations', 'routes' => 'Route management', 'discovery' => 'Framework discovery system', ]; foreach ($categories as $category => $commands) { $count = count($commands); $description = $categoryInfo[$category] ?? 'Various commands'; $categoryName = str_pad($category, 12); $commandCount = str_pad("({$count} commands)", 15); $this->output->writeLine( " {$categoryName} {$commandCount} {$description}", ConsoleColor::WHITE ); } $this->output->newLine(); $this->output->writeLine("Für Commands einer Kategorie: php {$this->scriptName} ", ConsoleColor::CYAN); } /** * Zeigt Commands einer spezifischen Kategorie */ private function showCategoryCommands(string $category, array $commands): void { $this->output->writeLine("Commands der Kategorie '{$category}':", ConsoleColor::BRIGHT_CYAN); $this->output->newLine(); foreach ($commands as $command) { $description = $command->description ?: 'Keine Beschreibung verfügbar'; $commandName = str_pad($command->name, 25); $this->output->writeLine(" {$commandName} {$description}", ConsoleColor::WHITE); } $this->output->newLine(); $this->output->writeLine("Verwendung:", ConsoleColor::BRIGHT_YELLOW); $this->output->writeLine(" php {$this->scriptName} [argumente]"); $this->output->writeLine(" php {$this->scriptName} help # Für detaillierte Hilfe"); } /** * Zeigt detaillierte Hilfe für ein spezifisches Kommando */ private function showCommandHelp(string $commandName): void { $commandList = $this->commandRegistry->getCommandList(); if (! $commandList->has($commandName)) { $this->errorHandler->handleCommandNotFound($commandName, $this->output); return; } $command = $commandList->get($commandName); $this->output->writeLine("Kommando: {$command->name}", ConsoleColor::BRIGHT_CYAN); $this->output->newLine(); if ($command->description) { $this->output->writeLine("Beschreibung:", ConsoleColor::BRIGHT_YELLOW); $this->output->writeLine(" {$command->description}"); $this->output->newLine(); } // Versuche Parameter-Informationen anzuzeigen try { $this->showCommandParameters($command); } catch (Throwable $e) { // Fallback zu Standard-Verwendung $this->output->writeLine("Verwendung:", ConsoleColor::BRIGHT_YELLOW); $this->output->writeLine(" php {$this->scriptName} {$command->name} [argumente]"); } } /** * Zeigt Parameter-Informationen für ein Kommando */ private function showCommandParameters(ConsoleCommand $command): void { try { // Hole die DiscoveredAttribute für das Command $discoveredAttribute = $this->commandRegistry->getDiscoveredAttribute($command->name); // Hole Reflection Information $reflection = new \ReflectionMethod($discoveredAttribute->className->toString(), $discoveredAttribute->methodName->toString()); // Prüfe ob es moderne Parameter-Parsing verwendet if ($this->commandRegistry->usesReflectionParameters($reflection)) { // Nutze den CommandParameterResolver für moderne Parameter-Hilfe $resolver = $this->container->resolve(CommandParameterResolver::class); $help = $resolver->generateMethodHelp($reflection, $command->name); $this->output->writeLine($help); } else { // Fallback für Legacy-Commands $this->output->writeLine("Verwendung:", ConsoleColor::BRIGHT_YELLOW); $this->output->writeLine(" php {$this->scriptName} {$command->name} [argumente]"); } } catch (\Throwable $e) { // Fallback bei Fehlern $this->output->writeLine("Verwendung:", ConsoleColor::BRIGHT_YELLOW); $this->output->writeLine(" php {$this->scriptName} {$command->name} [argumente]"); } } /** * Startet die interaktive TUI */ private function launchInteractiveTUI(): int { try { // Prüfe ob Terminal kompatibel ist if (! $this->isTerminalCompatible()) { $this->output->writeError("Interactive TUI requires a compatible terminal."); $this->output->writeLine("Use 'php {$this->scriptName} help' for command-line help."); return ExitCode::SOFTWARE_ERROR->value; } // Get DiscoveryRegistry for TUI components $discoveryRegistry = $this->container->get(DiscoveryRegistry::class); // Create CommandHistory $commandHistory = new CommandHistory(); // Create new services $groupRegistry = new CommandGroupRegistry($discoveryRegistry); $workflowExecutor = new SimpleWorkflowExecutor($this->commandRegistry, $groupRegistry, $this->output); // Create TUI components $state = new TuiState(); $renderer = new TuiRenderer($this->output); $menuBar = $renderer->getMenuBar(); $commandExecutor = new TuiCommandExecutor( $this->output, $this->commandRegistry, $this->container, $discoveryRegistry, $commandHistory, new CommandValidator(), new CommandHelpGenerator(new ParameterInspector()), $this->scriptName ); $inputHandler = new TuiInputHandler($commandExecutor, $menuBar); // Erstelle TUI Instanz $tui = new ConsoleTUI( $this->output, $this->container, $discoveryRegistry, $state, $renderer, $inputHandler, $commandExecutor, $commandHistory, $groupRegistry, $workflowExecutor ); // Starte TUI return $tui->run()->value; } catch (Throwable $e) { $this->output->writeError("Failed to launch interactive TUI: " . $e->getMessage()); $config = $this->container->get(AppConfig::class); if ($config->isDevelopment()) { $this->output->writeLine("Stack trace:", ConsoleColor::RED); $this->output->writeLine($e->getTraceAsString()); } // Fallback to help $this->output->newLine(); $this->output->writeLine("Falling back to command-line help:", ConsoleColor::YELLOW); $this->showHelp(); return ExitCode::SOFTWARE_ERROR->value; } } /** * Prüft ob das Terminal für TUI kompatibel ist */ private function isTerminalCompatible(): bool { // Prüfe ob wir in einem Terminal sind if (! function_exists('posix_isatty') || ! posix_isatty(STDOUT)) { return false; } // Prüfe TERM environment variable $term = $_SERVER['TERM'] ?? ''; if (empty($term) || $term === 'dumb') { return false; } // Prüfe ob das Terminal interaktiv ist if (! stream_isatty(STDIN) || ! stream_isatty(STDOUT)) { return false; } return true; } }