refactor(console): extract responsibilities from ConsoleApplication
- Extract terminal compatibility checking to TerminalCompatibilityChecker - Extract input validation to ConsoleInputValidator - Extract command categorization to CommandCategorizer - Extract result processing to CommandResultProcessor - Extract help rendering to ConsoleHelpRenderer - Extract TUI/Dialog initialization to TuiFactory/DialogFactory - Extract signal handling to ConsoleSignalHandler (uses Pcntl module) - Remove legacy PCNTL fallback code - Reduce ConsoleApplication from 757 to ~340 lines (55% reduction) All changes maintain backward compatibility - no breaking changes.
This commit is contained in:
86
src/Framework/Console/CommandCategorizer.php
Normal file
86
src/Framework/Console/CommandCategorizer.php
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Framework\Console;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kategorisiert Commands basierend auf ihrem Präfix (vor dem Doppelpunkt).
|
||||||
|
*
|
||||||
|
* Bietet auch Metadaten für Kategorien (Beschreibungen).
|
||||||
|
*/
|
||||||
|
final readonly class CommandCategorizer
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Kategorie-Informationen mit Beschreibungen.
|
||||||
|
*
|
||||||
|
* @var array<string, string>
|
||||||
|
*/
|
||||||
|
private const CATEGORY_INFO = [
|
||||||
|
'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',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kategorisiert Commands basierend auf ihrem Präfix.
|
||||||
|
*
|
||||||
|
* @param CommandList $commandList Liste der Commands
|
||||||
|
* @return array<string, array<ConsoleCommand>> Kategorien mit ihren Commands
|
||||||
|
*/
|
||||||
|
public function categorize(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt die Beschreibung für eine Kategorie zurück.
|
||||||
|
*
|
||||||
|
* @param string $category Kategorie-Name
|
||||||
|
* @return string Beschreibung oder 'Various commands' als Fallback
|
||||||
|
*/
|
||||||
|
public function getCategoryDescription(string $category): string
|
||||||
|
{
|
||||||
|
return self::CATEGORY_INFO[$category] ?? 'Various commands';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt alle Kategorie-Informationen zurück.
|
||||||
|
*
|
||||||
|
* @return array<string, string> Kategorie-Name => Beschreibung
|
||||||
|
*/
|
||||||
|
public function getCategoryInfo(): array
|
||||||
|
{
|
||||||
|
return self::CATEGORY_INFO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
68
src/Framework/Console/CommandResultProcessor.php
Normal file
68
src/Framework/Console/CommandResultProcessor.php
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Framework\Console;
|
||||||
|
|
||||||
|
use App\Framework\Console\Result\ConsoleResult;
|
||||||
|
use App\Framework\DI\Container;
|
||||||
|
use App\Framework\Logging\Logger;
|
||||||
|
use App\Framework\Logging\ValueObjects\LogContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verarbeitet Command-Results und konvertiert sie zu ExitCode.
|
||||||
|
*
|
||||||
|
* Unterstützt:
|
||||||
|
* - ConsoleResult (neue Pattern) - rendert und gibt exitCode zurück
|
||||||
|
* - ExitCode (Enum) - gibt direkt zurück
|
||||||
|
* - int (Legacy) - konvertiert zu ExitCode
|
||||||
|
* - Invalid - loggt Warnung und gibt GENERAL_ERROR zurück
|
||||||
|
*/
|
||||||
|
final readonly class CommandResultProcessor
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private Container $container,
|
||||||
|
private ConsoleOutputInterface $output
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verarbeitet ein Command-Result und gibt den ExitCode zurück.
|
||||||
|
*
|
||||||
|
* @param mixed $result Command-Result (ConsoleResult, ExitCode, int, oder ungültig)
|
||||||
|
* @return ExitCode Exit-Code für den Prozess
|
||||||
|
*/
|
||||||
|
public function process(mixed $result): ExitCode
|
||||||
|
{
|
||||||
|
// New ConsoleResult pattern
|
||||||
|
if ($result instanceof 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' => 'CommandResultProcessor',
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ExitCode::GENERAL_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
120
src/Framework/Console/Components/DialogFactory.php
Normal file
120
src/Framework/Console/Components/DialogFactory.php
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Framework\Console\Components;
|
||||||
|
|
||||||
|
use App\Framework\Config\AppConfig;
|
||||||
|
use App\Framework\Console\CommandGroupRegistry;
|
||||||
|
use App\Framework\Console\CommandHistory;
|
||||||
|
use App\Framework\Console\CommandRegistry;
|
||||||
|
use App\Framework\Console\ConsoleColor;
|
||||||
|
use App\Framework\Console\ConsoleOutputInterface;
|
||||||
|
use App\Framework\Console\ExitCode;
|
||||||
|
use App\Framework\Console\Help\ConsoleHelpRenderer;
|
||||||
|
use App\Framework\DI\Container;
|
||||||
|
use App\Framework\Discovery\Results\DiscoveryRegistry;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory für Dialog-Modus Initialisierung.
|
||||||
|
*
|
||||||
|
* Kapselt die Dependency-Injection und Fehlerbehandlung für Dialog-Modus.
|
||||||
|
*/
|
||||||
|
final readonly class DialogFactory
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private Container $container,
|
||||||
|
private ConsoleOutputInterface $output,
|
||||||
|
private CommandRegistry $commandRegistry,
|
||||||
|
private string $scriptName
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erstellt und startet eine Dialog-Instanz.
|
||||||
|
*
|
||||||
|
* @return int Exit-Code
|
||||||
|
*/
|
||||||
|
public function createAndRun(): int
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// Get DiscoveryRegistry for dialog components
|
||||||
|
$discoveryRegistry = $this->container->get(DiscoveryRegistry::class);
|
||||||
|
|
||||||
|
// Create CommandHistory
|
||||||
|
$commandHistory = new CommandHistory();
|
||||||
|
|
||||||
|
// Create new services
|
||||||
|
$groupRegistry = new CommandGroupRegistry($discoveryRegistry);
|
||||||
|
$commandList = $this->commandRegistry->getCommandList();
|
||||||
|
|
||||||
|
// Create dialog components
|
||||||
|
$commandExecutor = new DialogCommandExecutor(
|
||||||
|
$this->output,
|
||||||
|
$this->commandRegistry,
|
||||||
|
$commandHistory,
|
||||||
|
$this->scriptName
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create dialog instance
|
||||||
|
$dialog = new ConsoleDialog(
|
||||||
|
$this->output,
|
||||||
|
$discoveryRegistry,
|
||||||
|
$commandHistory,
|
||||||
|
$groupRegistry,
|
||||||
|
$commandExecutor,
|
||||||
|
$commandList,
|
||||||
|
$this->container,
|
||||||
|
$this->scriptName . '> '
|
||||||
|
);
|
||||||
|
|
||||||
|
// Start dialog
|
||||||
|
return $dialog->run()->value;
|
||||||
|
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
$this->output->writeError("Failed to launch dialog mode: " . $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->showHelpFallback();
|
||||||
|
|
||||||
|
return ExitCode::SOFTWARE_ERROR->value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zeigt Help als Fallback an.
|
||||||
|
*/
|
||||||
|
private function showHelpFallback(): void
|
||||||
|
{
|
||||||
|
$commandList = $this->commandRegistry->getCommandList();
|
||||||
|
|
||||||
|
// Create minimal error handler for help rendering
|
||||||
|
$suggestionEngine = new \App\Framework\Console\ErrorRecovery\CommandSuggestionEngine($commandList);
|
||||||
|
$recoveryService = new \App\Framework\Console\ErrorRecovery\ErrorRecoveryService(
|
||||||
|
$suggestionEngine,
|
||||||
|
$commandList,
|
||||||
|
$this->commandRegistry
|
||||||
|
);
|
||||||
|
$logger = $this->container->has(\App\Framework\Logging\Logger::class) ? $this->container->get(\App\Framework\Logging\Logger::class) : null;
|
||||||
|
$errorHandler = new \App\Framework\Console\ErrorRecovery\ConsoleErrorHandler($recoveryService, $logger);
|
||||||
|
|
||||||
|
$helpRenderer = new ConsoleHelpRenderer(
|
||||||
|
$this->output,
|
||||||
|
$this->commandRegistry,
|
||||||
|
$this->container,
|
||||||
|
$errorHandler,
|
||||||
|
$this->scriptName
|
||||||
|
);
|
||||||
|
$helpRenderer->showHelp($commandList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
154
src/Framework/Console/Components/TuiFactory.php
Normal file
154
src/Framework/Console/Components/TuiFactory.php
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Framework\Console\Components;
|
||||||
|
|
||||||
|
use App\Framework\Config\AppConfig;
|
||||||
|
use App\Framework\Console\Animation\AnimationManager;
|
||||||
|
use App\Framework\Console\CommandGroupRegistry;
|
||||||
|
use App\Framework\Console\CommandHelpGenerator;
|
||||||
|
use App\Framework\Console\CommandHistory;
|
||||||
|
use App\Framework\Console\CommandRegistry;
|
||||||
|
use App\Framework\Console\CommandValidator;
|
||||||
|
use App\Framework\Console\ConsoleColor;
|
||||||
|
use App\Framework\Console\ConsoleOutputInterface;
|
||||||
|
use App\Framework\Console\ExitCode;
|
||||||
|
use App\Framework\Console\Help\ConsoleHelpRenderer;
|
||||||
|
use App\Framework\Console\ParameterInspector;
|
||||||
|
use App\Framework\Console\SimpleWorkflowExecutor;
|
||||||
|
use App\Framework\Console\Terminal\TerminalCompatibilityChecker;
|
||||||
|
use App\Framework\DI\Container;
|
||||||
|
use App\Framework\Discovery\Results\DiscoveryRegistry;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory für TUI (Text User Interface) Initialisierung.
|
||||||
|
*
|
||||||
|
* Kapselt die Dependency-Injection und Fehlerbehandlung für TUI.
|
||||||
|
*/
|
||||||
|
final readonly class TuiFactory
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private Container $container,
|
||||||
|
private ConsoleOutputInterface $output,
|
||||||
|
private CommandRegistry $commandRegistry,
|
||||||
|
private string $scriptName
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erstellt und startet eine TUI-Instanz.
|
||||||
|
*
|
||||||
|
* @return int Exit-Code
|
||||||
|
*/
|
||||||
|
public function createAndRun(): int
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// Prüfe ob Terminal kompatibel ist
|
||||||
|
$checker = TerminalCompatibilityChecker::create();
|
||||||
|
if (!$checker->isTuiCompatible()) {
|
||||||
|
$this->output->writeError("Interactive TUI requires a compatible terminal.");
|
||||||
|
$this->output->writeLine("Use 'php {$this->scriptName} help' for command-line help.");
|
||||||
|
error_log("TUI: Terminal compatibility check failed - TERM=" . ($_SERVER['TERM'] ?? 'not set') . ", isatty(STDOUT)=" . (function_exists('posix_isatty') && posix_isatty(STDOUT) ? 'true' : 'false'));
|
||||||
|
|
||||||
|
return ExitCode::SOFTWARE_ERROR->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
error_log("TUI: Starting interactive TUI initialization...");
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
// Create animation manager first, then pass it to renderer
|
||||||
|
$animationManager = new AnimationManager();
|
||||||
|
$renderer = new TuiRenderer($this->output, $animationManager);
|
||||||
|
$commandExecutor = new TuiCommandExecutor(
|
||||||
|
$this->output,
|
||||||
|
$this->commandRegistry,
|
||||||
|
$this->container,
|
||||||
|
$discoveryRegistry,
|
||||||
|
$commandHistory,
|
||||||
|
new CommandValidator(),
|
||||||
|
new CommandHelpGenerator(new ParameterInspector()),
|
||||||
|
$this->scriptName
|
||||||
|
);
|
||||||
|
|
||||||
|
// Erstelle TUI Instanz
|
||||||
|
$tui = new ConsoleTUI(
|
||||||
|
$this->output,
|
||||||
|
$this->container,
|
||||||
|
$discoveryRegistry,
|
||||||
|
$state,
|
||||||
|
$renderer,
|
||||||
|
$commandExecutor,
|
||||||
|
$commandHistory,
|
||||||
|
$groupRegistry,
|
||||||
|
$workflowExecutor
|
||||||
|
);
|
||||||
|
|
||||||
|
// Starte TUI
|
||||||
|
return $tui->run()->value;
|
||||||
|
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
$this->output->writeError("Failed to launch interactive TUI: " . $e->getMessage());
|
||||||
|
|
||||||
|
// Always log the full error for debugging
|
||||||
|
error_log("TUI Launch Error: " . $e->getMessage());
|
||||||
|
error_log("TUI Launch Error Class: " . $e::class);
|
||||||
|
error_log("TUI Launch Error File: " . $e->getFile() . ":" . $e->getLine());
|
||||||
|
error_log("TUI Launch Error Trace: " . $e->getTraceAsString());
|
||||||
|
|
||||||
|
$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->showHelpFallback();
|
||||||
|
|
||||||
|
return ExitCode::SOFTWARE_ERROR->value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zeigt Help als Fallback an.
|
||||||
|
*/
|
||||||
|
private function showHelpFallback(): void
|
||||||
|
{
|
||||||
|
$discoveryRegistry = $this->container->get(DiscoveryRegistry::class);
|
||||||
|
$commandList = $this->commandRegistry->getCommandList();
|
||||||
|
|
||||||
|
// Create minimal error handler for help rendering
|
||||||
|
$suggestionEngine = new \App\Framework\Console\ErrorRecovery\CommandSuggestionEngine($commandList);
|
||||||
|
$recoveryService = new \App\Framework\Console\ErrorRecovery\ErrorRecoveryService(
|
||||||
|
$suggestionEngine,
|
||||||
|
$commandList,
|
||||||
|
$this->commandRegistry
|
||||||
|
);
|
||||||
|
$logger = $this->container->has(\App\Framework\Logging\Logger::class) ? $this->container->get(\App\Framework\Logging\Logger::class) : null;
|
||||||
|
$errorHandler = new \App\Framework\Console\ErrorRecovery\ConsoleErrorHandler($recoveryService, $logger);
|
||||||
|
|
||||||
|
$helpRenderer = new ConsoleHelpRenderer(
|
||||||
|
$this->output,
|
||||||
|
$this->commandRegistry,
|
||||||
|
$this->container,
|
||||||
|
$errorHandler,
|
||||||
|
$this->scriptName
|
||||||
|
);
|
||||||
|
$helpRenderer->showHelp($commandList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -4,24 +4,24 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Framework\Console;
|
namespace App\Framework\Console;
|
||||||
|
|
||||||
use App\Framework\Config\AppConfig;
|
use App\Framework\Cache\Cache;
|
||||||
use App\Framework\Console\Components\ConsoleDialog;
|
|
||||||
use App\Framework\Console\Components\ConsoleTUI;
|
|
||||||
use App\Framework\Console\Components\DialogCommandExecutor;
|
|
||||||
use App\Framework\Console\Components\TuiCommandExecutor;
|
|
||||||
use App\Framework\Console\Components\TuiInputHandler;
|
|
||||||
use App\Framework\Console\Components\TuiRenderer;
|
|
||||||
use App\Framework\Console\Components\TuiState;
|
|
||||||
use App\Framework\Console\ErrorRecovery\CommandSuggestionEngine;
|
use App\Framework\Console\ErrorRecovery\CommandSuggestionEngine;
|
||||||
use App\Framework\Console\ErrorRecovery\ConsoleErrorHandler;
|
use App\Framework\Console\ErrorRecovery\ConsoleErrorHandler;
|
||||||
use App\Framework\Console\ErrorRecovery\ErrorRecoveryService;
|
use App\Framework\Console\ErrorRecovery\ErrorRecoveryService;
|
||||||
|
use App\Framework\Console\Exceptions\ConsoleException;
|
||||||
|
use App\Framework\Console\Exceptions\ConsoleInitializationException;
|
||||||
use App\Framework\Console\Exceptions\CommandNotFoundException;
|
use App\Framework\Console\Exceptions\CommandNotFoundException;
|
||||||
|
use App\Framework\Console\Help\ConsoleHelpRenderer;
|
||||||
|
use App\Framework\Console\Terminal\TerminalCompatibilityChecker;
|
||||||
|
use App\Framework\Console\Validation\ConsoleInputValidator;
|
||||||
|
use App\Framework\Core\PathProvider;
|
||||||
|
use App\Framework\DateTime\Clock;
|
||||||
use App\Framework\DI\Container;
|
use App\Framework\DI\Container;
|
||||||
|
use App\Framework\Discovery\DiscoveryServiceBootstrapper;
|
||||||
use App\Framework\Discovery\Results\DiscoveryRegistry;
|
use App\Framework\Discovery\Results\DiscoveryRegistry;
|
||||||
use App\Framework\Exception\ErrorCode;
|
|
||||||
use App\Framework\Exception\FrameworkException;
|
|
||||||
use App\Framework\Logging\Logger;
|
use App\Framework\Logging\Logger;
|
||||||
use App\Framework\Logging\ValueObjects\LogContext;
|
use App\Framework\Logging\ValueObjects\LogContext;
|
||||||
|
use App\Framework\Pcntl\ValueObjects\Signal;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
|
||||||
final class ConsoleApplication
|
final class ConsoleApplication
|
||||||
@@ -34,6 +34,8 @@ final class ConsoleApplication
|
|||||||
|
|
||||||
private ConsoleErrorHandler $errorHandler;
|
private ConsoleErrorHandler $errorHandler;
|
||||||
|
|
||||||
|
private ConsoleSignalHandler $signalHandler;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly Container $container,
|
private readonly Container $container,
|
||||||
private readonly string $scriptName = 'console',
|
private readonly string $scriptName = 'console',
|
||||||
@@ -43,7 +45,13 @@ final class ConsoleApplication
|
|||||||
$this->output = $output ?? new ConsoleOutput();
|
$this->output = $output ?? new ConsoleOutput();
|
||||||
|
|
||||||
// Setup signal handlers für graceful shutdown
|
// Setup signal handlers für graceful shutdown
|
||||||
$this->setupSignalHandlers();
|
$this->signalHandler = new ConsoleSignalHandler(
|
||||||
|
$this->container,
|
||||||
|
function (Signal $signal) {
|
||||||
|
$this->handleShutdown($signal);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
$this->signalHandler->setupShutdownHandlers();
|
||||||
|
|
||||||
// Setze den Fenstertitel
|
// Setze den Fenstertitel
|
||||||
$this->output->writeWindowTitle($this->title);
|
$this->output->writeWindowTitle($this->title);
|
||||||
@@ -56,9 +64,9 @@ final class ConsoleApplication
|
|||||||
error_log("Console initialization failed: " . $e->getMessage());
|
error_log("Console initialization failed: " . $e->getMessage());
|
||||||
error_log("Stack trace: " . $e->getTraceAsString());
|
error_log("Stack trace: " . $e->getTraceAsString());
|
||||||
|
|
||||||
throw FrameworkException::create(
|
throw new ConsoleInitializationException(
|
||||||
ErrorCode::SYS_INITIALIZATION_FAILED,
|
'Initialisierung der Console-Anwendung ist fehlgeschlagen.',
|
||||||
'Failed to initialize console application: ' . $e->getMessage()
|
$e
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -79,25 +87,17 @@ final class ConsoleApplication
|
|||||||
$this->errorHandler = new ConsoleErrorHandler($recoveryService, $logger);
|
$this->errorHandler = new ConsoleErrorHandler($recoveryService, $logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function setupSignalHandlers(): void
|
public function handleShutdown(Signal $signal): 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->shutdownRequested = true;
|
||||||
$this->output->writeLine("Shutdown signal received ({$signal}). Cleaning up...", ConsoleColor::YELLOW);
|
$this->output->writeLine("Shutdown signal received ({$signal->getName()}). Cleaning up...", ConsoleColor::YELLOW);
|
||||||
|
|
||||||
// Cleanup resources
|
// Cleanup resources
|
||||||
$this->cleanup();
|
$this->cleanup();
|
||||||
exit(ExitCode::SUCCESS->value);
|
exit(ExitCode::SUCCESS->value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private function cleanup(): void
|
private function cleanup(): void
|
||||||
{
|
{
|
||||||
// Reset window title
|
// Reset window title
|
||||||
@@ -121,14 +121,15 @@ final class ConsoleApplication
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Force fresh discovery
|
// Force fresh discovery
|
||||||
$bootstrapper = new \App\Framework\Discovery\DiscoveryServiceBootstrapper(
|
$bootstrapper = new DiscoveryServiceBootstrapper(
|
||||||
$this->container,
|
$this->container,
|
||||||
$this->container->get(\App\Framework\DateTime\Clock::class)
|
$this->container->get(Clock::class),
|
||||||
|
$this->container->get(Logger::class),
|
||||||
);
|
);
|
||||||
|
|
||||||
$freshRegistry = $bootstrapper->performBootstrap(
|
$freshRegistry = $bootstrapper->performBootstrap(
|
||||||
$this->container->get(\App\Framework\Core\PathProvider::class),
|
$this->container->get(PathProvider::class),
|
||||||
$this->container->get(\App\Framework\Cache\Cache::class),
|
$this->container->get(Cache::class),
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -160,9 +161,7 @@ final class ConsoleApplication
|
|||||||
$argv = $this->validateAndSanitizeInput($argv);
|
$argv = $this->validateAndSanitizeInput($argv);
|
||||||
|
|
||||||
// Check for shutdown signal
|
// Check for shutdown signal
|
||||||
if (function_exists('pcntl_signal_dispatch')) {
|
$this->signalHandler->dispatchSignals();
|
||||||
pcntl_signal_dispatch();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->shutdownRequested) {
|
if ($this->shutdownRequested) {
|
||||||
return ExitCode::INTERRUPTED->value;
|
return ExitCode::INTERRUPTED->value;
|
||||||
@@ -188,11 +187,20 @@ final class ConsoleApplication
|
|||||||
|
|
||||||
// Handle built-in commands
|
// Handle built-in commands
|
||||||
if (in_array($commandName, ['help', '--help', '-h'])) {
|
if (in_array($commandName, ['help', '--help', '-h'])) {
|
||||||
|
$helpRenderer = new ConsoleHelpRenderer(
|
||||||
|
$this->output,
|
||||||
|
$this->commandRegistry,
|
||||||
|
$this->container,
|
||||||
|
$this->errorHandler,
|
||||||
|
$this->scriptName
|
||||||
|
);
|
||||||
|
$commandList = $this->commandRegistry->getCommandList();
|
||||||
|
|
||||||
// Spezifische Command-Hilfe
|
// Spezifische Command-Hilfe
|
||||||
if (!empty($arguments) && isset($arguments[0])) {
|
if (!empty($arguments) && isset($arguments[0])) {
|
||||||
$this->showCommandHelp($arguments[0]);
|
$helpRenderer->showCommandHelp($arguments[0], $commandList);
|
||||||
} else {
|
} else {
|
||||||
$this->showHelp();
|
$helpRenderer->showHelp($commandList);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ExitCode::SUCCESS->value;
|
return ExitCode::SUCCESS->value;
|
||||||
@@ -206,9 +214,17 @@ final class ConsoleApplication
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Prüfe ob es eine Kategorie ist
|
// Prüfe ob es eine Kategorie ist
|
||||||
$categories = $this->categorizeCommands($commandList);
|
$categorizer = new CommandCategorizer();
|
||||||
|
$categories = $categorizer->categorize($commandList);
|
||||||
if (isset($categories[$commandName])) {
|
if (isset($categories[$commandName])) {
|
||||||
$this->showCategoryCommands($commandName, $categories[$commandName]);
|
$helpRenderer = new ConsoleHelpRenderer(
|
||||||
|
$this->output,
|
||||||
|
$this->commandRegistry,
|
||||||
|
$this->container,
|
||||||
|
$this->errorHandler,
|
||||||
|
$this->scriptName
|
||||||
|
);
|
||||||
|
$helpRenderer->showCategoryCommands($commandName, $categories[$commandName]);
|
||||||
|
|
||||||
return ExitCode::SUCCESS->value;
|
return ExitCode::SUCCESS->value;
|
||||||
}
|
}
|
||||||
@@ -230,31 +246,8 @@ final class ConsoleApplication
|
|||||||
*/
|
*/
|
||||||
private function validateAndSanitizeInput(array $argv): array
|
private function validateAndSanitizeInput(array $argv): array
|
||||||
{
|
{
|
||||||
if (empty($argv)) {
|
$validator = new ConsoleInputValidator();
|
||||||
throw new \InvalidArgumentException('No arguments provided');
|
return $validator->validateAndSanitize($argv);
|
||||||
}
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -264,9 +257,7 @@ final class ConsoleApplication
|
|||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
// Check for shutdown signal before execution
|
// Check for shutdown signal before execution
|
||||||
if (function_exists('pcntl_signal_dispatch')) {
|
$this->signalHandler->dispatchSignals();
|
||||||
pcntl_signal_dispatch();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->shutdownRequested) {
|
if ($this->shutdownRequested) {
|
||||||
return ExitCode::INTERRUPTED;
|
return ExitCode::INTERRUPTED;
|
||||||
@@ -279,12 +270,13 @@ final class ConsoleApplication
|
|||||||
$result = $this->commandRegistry->executeCommand($commandName, $arguments, $this->output);
|
$result = $this->commandRegistry->executeCommand($commandName, $arguments, $this->output);
|
||||||
|
|
||||||
// Handle ConsoleResult (new) or ExitCode (legacy)
|
// Handle ConsoleResult (new) or ExitCode (legacy)
|
||||||
return $this->processCommandResult($result);
|
$processor = new CommandResultProcessor($this->container, $this->output);
|
||||||
|
return $processor->process($result);
|
||||||
|
|
||||||
} catch (CommandNotFoundException $e) {
|
} catch (CommandNotFoundException $e) {
|
||||||
return $this->errorHandler->handleCommandNotFound($commandName, $this->output);
|
return $this->errorHandler->handleCommandNotFound($commandName, $this->output);
|
||||||
|
|
||||||
} catch (FrameworkException $e) {
|
} catch (ConsoleException $e) {
|
||||||
return $this->errorHandler->handleCommandExecutionError($commandName, $e, $this->output);
|
return $this->errorHandler->handleCommandExecutionError($commandName, $e, $this->output);
|
||||||
|
|
||||||
} catch (\InvalidArgumentException $e) {
|
} catch (\InvalidArgumentException $e) {
|
||||||
@@ -301,327 +293,20 @@ final class ConsoleApplication
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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} --dialog # Dialog-Modus starten (AI-Assistent-ähnlich)");
|
|
||||||
$this->output->writeLine(" php {$this->scriptName} --chat # Dialog-Modus starten (Alias)");
|
|
||||||
$this->output->writeLine(" php {$this->scriptName} <kategorie> # Commands einer Kategorie anzeigen");
|
|
||||||
$this->output->writeLine(" php {$this->scriptName} <kommando> [argumente] # Kommando direkt ausführen");
|
|
||||||
$this->output->writeLine(" php {$this->scriptName} help <kommando> # 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.");
|
|
||||||
$this->output->writeLine(" Der Dialog-Modus bietet eine einfache Text-Eingabe mit Tab-Completion und History.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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} <kategorie>", 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} <kommando> [argumente]");
|
|
||||||
$this->output->writeLine(" php {$this->scriptName} help <kommando> # 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
|
* Startet die interaktive TUI
|
||||||
*/
|
*/
|
||||||
private function launchInteractiveTUI(): int
|
private function launchInteractiveTUI(): int
|
||||||
{
|
{
|
||||||
try {
|
$factory = new Components\TuiFactory(
|
||||||
// Prüfe ob Terminal kompatibel ist
|
$this->container,
|
||||||
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->output,
|
||||||
$this->commandRegistry,
|
$this->commandRegistry,
|
||||||
$this->container,
|
|
||||||
$discoveryRegistry,
|
|
||||||
$commandHistory,
|
|
||||||
new CommandValidator(),
|
|
||||||
new CommandHelpGenerator(new ParameterInspector()),
|
|
||||||
$this->scriptName
|
$this->scriptName
|
||||||
);
|
);
|
||||||
$inputHandler = new TuiInputHandler($commandExecutor, $menuBar);
|
|
||||||
|
|
||||||
// Erstelle TUI Instanz
|
return $factory->createAndRun();
|
||||||
$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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -629,56 +314,14 @@ final class ConsoleApplication
|
|||||||
*/
|
*/
|
||||||
private function launchDialogMode(): int
|
private function launchDialogMode(): int
|
||||||
{
|
{
|
||||||
try {
|
$factory = new Components\DialogFactory(
|
||||||
// Get DiscoveryRegistry for dialog components
|
$this->container,
|
||||||
$discoveryRegistry = $this->container->get(DiscoveryRegistry::class);
|
|
||||||
|
|
||||||
// Create CommandHistory
|
|
||||||
$commandHistory = new CommandHistory();
|
|
||||||
|
|
||||||
// Create new services
|
|
||||||
$groupRegistry = new CommandGroupRegistry($discoveryRegistry);
|
|
||||||
$commandList = $this->commandRegistry->getCommandList();
|
|
||||||
|
|
||||||
// Create dialog components
|
|
||||||
$commandExecutor = new DialogCommandExecutor(
|
|
||||||
$this->output,
|
$this->output,
|
||||||
$this->commandRegistry,
|
$this->commandRegistry,
|
||||||
$commandHistory,
|
|
||||||
$this->scriptName
|
$this->scriptName
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create dialog instance
|
return $factory->createAndRun();
|
||||||
$dialog = new ConsoleDialog(
|
|
||||||
$this->output,
|
|
||||||
$discoveryRegistry,
|
|
||||||
$commandHistory,
|
|
||||||
$groupRegistry,
|
|
||||||
$commandExecutor,
|
|
||||||
$commandList,
|
|
||||||
$this->container,
|
|
||||||
$this->scriptName . '> '
|
|
||||||
);
|
|
||||||
|
|
||||||
// Start dialog
|
|
||||||
return $dialog->run()->value;
|
|
||||||
|
|
||||||
} catch (Throwable $e) {
|
|
||||||
$this->output->writeError("Failed to launch dialog mode: " . $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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -686,22 +329,7 @@ final class ConsoleApplication
|
|||||||
*/
|
*/
|
||||||
private function isTerminalCompatible(): bool
|
private function isTerminalCompatible(): bool
|
||||||
{
|
{
|
||||||
// Prüfe ob wir in einem Terminal sind
|
$checker = TerminalCompatibilityChecker::create();
|
||||||
if (! function_exists('posix_isatty') || ! posix_isatty(STDOUT)) {
|
return $checker->isTuiCompatible();
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
74
src/Framework/Console/ConsoleSignalHandler.php
Normal file
74
src/Framework/Console/ConsoleSignalHandler.php
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Framework\Console;
|
||||||
|
|
||||||
|
use App\Framework\DI\Container;
|
||||||
|
use App\Framework\Pcntl\PcntlService;
|
||||||
|
use App\Framework\Pcntl\ValueObjects\Signal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kapselt Signal-Handler-Setup für ConsoleApplication.
|
||||||
|
*
|
||||||
|
* Nutzt ausschließlich PcntlService aus dem Pcntl-Modul.
|
||||||
|
* Keine direkten PCNTL-Funktionsaufrufe mehr.
|
||||||
|
*/
|
||||||
|
final readonly class ConsoleSignalHandler
|
||||||
|
{
|
||||||
|
private ?PcntlService $pcntlService = null;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private Container $container,
|
||||||
|
private callable $shutdownCallback
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup shutdown handlers für SIGTERM, SIGINT, SIGHUP.
|
||||||
|
*
|
||||||
|
* Nutzt PcntlService wenn verfügbar, sonst wird kein Signal-Handling eingerichtet.
|
||||||
|
*/
|
||||||
|
public function setupShutdownHandlers(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->pcntlService = $this->container->get(PcntlService::class);
|
||||||
|
|
||||||
|
$this->pcntlService->registerSignal(Signal::SIGTERM, function (Signal $signal) {
|
||||||
|
($this->shutdownCallback)($signal);
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->pcntlService->registerSignal(Signal::SIGINT, function (Signal $signal) {
|
||||||
|
($this->shutdownCallback)($signal);
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->pcntlService->registerSignal(Signal::SIGHUP, function (Signal $signal) {
|
||||||
|
($this->shutdownCallback)($signal);
|
||||||
|
});
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
// PCNTL not available or not registered, ignore
|
||||||
|
// Kein Fallback mehr - Signal-Handling ist optional
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatch pending signals.
|
||||||
|
*
|
||||||
|
* Sollte regelmäßig in lang laufenden Prozessen aufgerufen werden.
|
||||||
|
*/
|
||||||
|
public function dispatchSignals(): void
|
||||||
|
{
|
||||||
|
if ($this->pcntlService !== null) {
|
||||||
|
$this->pcntlService->dispatchSignals();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prüft ob Signal-Handling verfügbar ist.
|
||||||
|
*/
|
||||||
|
public function isAvailable(): bool
|
||||||
|
{
|
||||||
|
return $this->pcntlService !== null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
207
src/Framework/Console/Help/ConsoleHelpRenderer.php
Normal file
207
src/Framework/Console/Help/ConsoleHelpRenderer.php
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Framework\Console\Help;
|
||||||
|
|
||||||
|
use App\Framework\Console\CommandCategorizer;
|
||||||
|
use App\Framework\Console\CommandList;
|
||||||
|
use App\Framework\Console\CommandParameterResolver;
|
||||||
|
use App\Framework\Console\CommandRegistry;
|
||||||
|
use App\Framework\Console\ConsoleColor;
|
||||||
|
use App\Framework\Console\ConsoleCommand;
|
||||||
|
use App\Framework\Console\ConsoleOutputInterface;
|
||||||
|
use App\Framework\Console\ErrorRecovery\ConsoleErrorHandler;
|
||||||
|
use App\Framework\DI\Container;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rendert Help-Informationen für Console-Commands.
|
||||||
|
*
|
||||||
|
* Extrahiert alle Help-Generierungs-Logik aus ConsoleApplication.
|
||||||
|
*/
|
||||||
|
final readonly class ConsoleHelpRenderer
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private ConsoleOutputInterface $output,
|
||||||
|
private CommandRegistry $commandRegistry,
|
||||||
|
private Container $container,
|
||||||
|
private ConsoleErrorHandler $errorHandler,
|
||||||
|
private string $scriptName
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zeigt allgemeine Help-Informationen.
|
||||||
|
*/
|
||||||
|
public function showHelp(CommandList $commandList): void
|
||||||
|
{
|
||||||
|
$this->output->writeLine("Console Commands", ConsoleColor::BRIGHT_CYAN);
|
||||||
|
$this->output->newLine();
|
||||||
|
|
||||||
|
if ($commandList->isEmpty()) {
|
||||||
|
$this->output->writeLine(" Keine Kommandos verfügbar.", ConsoleColor::YELLOW);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kategorisiere Commands
|
||||||
|
$categorizer = new CommandCategorizer();
|
||||||
|
$categories = $categorizer->categorize($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} --dialog # Dialog-Modus starten (AI-Assistent-ähnlich)");
|
||||||
|
$this->output->writeLine(" php {$this->scriptName} --chat # Dialog-Modus starten (Alias)");
|
||||||
|
$this->output->writeLine(" php {$this->scriptName} <kategorie> # Commands einer Kategorie anzeigen");
|
||||||
|
$this->output->writeLine(" php {$this->scriptName} <kommando> [argumente] # Kommando direkt ausführen");
|
||||||
|
$this->output->writeLine(" php {$this->scriptName} help <kommando> # 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.");
|
||||||
|
$this->output->writeLine(" Der Dialog-Modus bietet eine einfache Text-Eingabe mit Tab-Completion und History.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zeigt detaillierte Hilfe für ein spezifisches Kommando.
|
||||||
|
*/
|
||||||
|
public function showCommandHelp(string $commandName, CommandList $commandList): void
|
||||||
|
{
|
||||||
|
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 eine übersichtliche Kategorien-Übersicht.
|
||||||
|
*/
|
||||||
|
public function showCategoryOverview(array $categories): void
|
||||||
|
{
|
||||||
|
$this->output->writeLine("Verfügbare Kategorien:", ConsoleColor::BRIGHT_YELLOW);
|
||||||
|
$this->output->newLine();
|
||||||
|
|
||||||
|
$categorizer = new CommandCategorizer();
|
||||||
|
|
||||||
|
foreach ($categories as $category => $commands) {
|
||||||
|
$count = count($commands);
|
||||||
|
$description = $categorizer->getCategoryDescription($category);
|
||||||
|
|
||||||
|
$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} <kategorie>", ConsoleColor::CYAN);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zeigt Commands einer spezifischen Kategorie.
|
||||||
|
*/
|
||||||
|
public 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} <kommando> [argumente]");
|
||||||
|
$this->output->writeLine(" php {$this->scriptName} help <kommando> # Für detaillierte Hilfe");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zeigt Verwendung für ein Kommando.
|
||||||
|
*/
|
||||||
|
public function showCommandUsage(string $commandName, CommandList $commandList): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Framework\Console\Terminal;
|
||||||
|
|
||||||
|
use App\Framework\Console\CliSapi;
|
||||||
|
use App\Framework\Console\TerminalDetector;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prüft ob das Terminal für TUI (Text User Interface) kompatibel ist.
|
||||||
|
*
|
||||||
|
* Nutzt TerminalDetector und TerminalCapabilities statt direkter
|
||||||
|
* posix_isatty/stream_isatty Aufrufe.
|
||||||
|
*/
|
||||||
|
final readonly class TerminalCompatibilityChecker
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private CliSapi $cliSapi
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prüft ob das Terminal für TUI kompatibel ist.
|
||||||
|
*
|
||||||
|
* Ein Terminal ist TUI-kompatibel wenn:
|
||||||
|
* - STDOUT ein Terminal ist
|
||||||
|
* - STDIN ein Terminal ist
|
||||||
|
* - TERM environment variable gesetzt und nicht "dumb" ist
|
||||||
|
*/
|
||||||
|
public function isTuiCompatible(): bool
|
||||||
|
{
|
||||||
|
// Prüfe ob STDOUT ein Terminal ist
|
||||||
|
if (!$this->cliSapi->isStdoutTerminal()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prüfe ob STDIN ein Terminal ist
|
||||||
|
if (!$this->cliSapi->isTerminal($this->cliSapi->stdin)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prüfe TERM environment variable
|
||||||
|
$term = getenv('TERM');
|
||||||
|
if ($term === false || $term === '' || strtolower($term) === 'dumb') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erstellt eine Instanz mit automatischer SAPI-Detection.
|
||||||
|
*/
|
||||||
|
public static function create(): self
|
||||||
|
{
|
||||||
|
return new self(CliSapi::detect());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
52
src/Framework/Console/Validation/ConsoleInputValidator.php
Normal file
52
src/Framework/Console/Validation/ConsoleInputValidator.php
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Framework\Console\Validation;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validiert und sanitized Console-Input (argv).
|
||||||
|
*
|
||||||
|
* Entfernt gefährliche Zeichen und prüft auf gültige Struktur.
|
||||||
|
*/
|
||||||
|
final readonly class ConsoleInputValidator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Validiert und sanitized argv Array.
|
||||||
|
*
|
||||||
|
* @param array<int, string> $argv Input arguments
|
||||||
|
* @return array<int, string> Sanitized arguments
|
||||||
|
* @throws InvalidArgumentException Bei ungültigen Eingaben
|
||||||
|
*/
|
||||||
|
public function validateAndSanitize(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user