*/ private array $discoveredAttributes; private CommandParameterResolver $parameterResolver; private ?ConsolePerformanceCollector $performanceCollector; private ProgressMiddleware $progressMiddleware; public function __construct( private Container $container, DiscoveryRegistry $discoveryRegistry ) { $this->parameterResolver = new CommandParameterResolver(new MethodSignatureAnalyzer()); $this->performanceCollector = $this->createPerformanceCollector(); $this->progressMiddleware = new ProgressMiddleware(); $this->discoverCommands($discoveryRegistry); } public function getCommandList(): CommandList { return $this->commandList; } public function getDiscoveredAttribute(string $commandName): DiscoveredAttribute { if (! isset($this->discoveredAttributes[$commandName])) { throw FrameworkException::create( ConsoleErrorCode::COMMAND_NOT_FOUND, "No discovered attribute found for command '{$commandName}'" )->withData(['command_name' => $commandName]); } return $this->discoveredAttributes[$commandName]; } /** * @param array $arguments */ public function executeCommand(string $commandName, array $arguments, ConsoleOutputInterface $output): ExitCode { if ($this->performanceCollector) { return $this->performanceCollector->measureCommand( $commandName, fn () => $this->doExecuteCommand($commandName, $arguments, $output), $arguments, $output ); } return $this->doExecuteCommand($commandName, $arguments, $output); } private function doExecuteCommand(string $commandName, array $arguments, ConsoleOutputInterface $output): ExitCode { $command = $this->commandList->get($commandName); $discoveredAttribute = $this->getDiscoveredAttribute($commandName); try { // Get execution context from discovered attribute $className = $discoveredAttribute->className->getFullyQualified(); $methodName = $discoveredAttribute->methodName?->toString() ?? '__invoke'; // Performance tracking: Container resolution $containerStart = microtime(true); $instance = $this->container->get($className); $containerDuration = (microtime(true) - $containerStart) * 1000; if ($this->performanceCollector) { $this->performanceCollector->recordContainerResolutionTime($commandName, $className, $containerDuration); } // Validate command structure if (! is_object($instance) || ! method_exists($instance, $methodName)) { throw FrameworkException::create( ConsoleErrorCode::INVALID_COMMAND_STRUCTURE, "Invalid command configuration for '{$commandName}'" )->withData([ 'command_name' => $commandName, 'class_name' => $className, 'method_name' => $methodName, ]); } // Get reflection method for parameter resolution $reflectionMethod = new ReflectionMethod($className, $methodName); // Performance tracking: Validation time $validationStart = microtime(true); $this->parameterResolver->validateMethodSignature($reflectionMethod); $validationDuration = (microtime(true) - $validationStart) * 1000; if ($this->performanceCollector) { $this->performanceCollector->recordValidationTime($commandName, $validationDuration); } // Execute command with automatic parameter resolution $result = $this->executeCommandWithReflection($instance, $reflectionMethod, $arguments, $output); return $this->normalizeCommandResult($result); } catch (Throwable $e) { throw FrameworkException::create( ConsoleErrorCode::EXECUTION_FAILED, "Failed to execute command '{$commandName}': {$e->getMessage()}" )->withData([ 'command_name' => $commandName, 'error_message' => $e->getMessage(), 'error_type' => get_class($e), ]); } } private function createPerformanceCollector(): ?ConsolePerformanceCollector { try { if ($this->container->has(PerformanceCollectorInterface::class)) { $performanceCollector = $this->container->get(PerformanceCollectorInterface::class); return new ConsolePerformanceCollector($performanceCollector); } } catch (Throwable $e) { // Performance monitoring is optional - don't fail if unavailable } return null; } private function discoverCommands(DiscoveryRegistry $discoveryRegistry): void { $commands = []; $discoveredAttributes = []; /** @var DiscoveredAttribute $discoveredAttribute */ foreach ($discoveryRegistry->attributes->get(ConsoleCommand::class) as $discoveredAttribute) { try { $registeredCommand = $this->registerDiscoveredCommand($discoveredAttribute); $commands[] = $registeredCommand; $discoveredAttributes[$registeredCommand->name] = $discoveredAttribute; } catch (Throwable $e) { // Log warning but continue with other commands if ($this->container->has(Logger::class)) { $logger = $this->container->get(Logger::class); $logger->warning('Failed to register command', LogContext::withData([ 'class_name' => $discoveredAttribute->className->getFullyQualified(), 'error' => $e->getMessage(), 'error_class' => get_class($e), 'component' => 'CommandRegistry', ])); } } } $this->commandList = new CommandList(...$commands); $this->discoveredAttributes = $discoveredAttributes; } private function registerDiscoveredCommand(DiscoveredAttribute $discoveredAttribute): ConsoleCommand { // Validate discovered attribute if (! $discoveredAttribute->className) { throw new \InvalidArgumentException('Missing class name in discovered attribute'); } $className = $discoveredAttribute->className->getFullyQualified(); // Validate class exists if (! class_exists($className)) { throw new \InvalidArgumentException("Command class {$className} does not exist"); } /** @var ConsoleCommand $command */ $command = $discoveredAttribute->createAttributeInstance(); // Validate command name if (empty(trim($command->name))) { throw new \InvalidArgumentException("Command name cannot be empty for class {$className}"); } $methodName = $discoveredAttribute->methodName?->toString() ?? '__invoke'; try { // Validate method exists and is callable $reflection = new ReflectionMethod($className, $methodName); if (! $reflection->isPublic()) { throw new \InvalidArgumentException("Command method {$className}::{$methodName} must be public"); } // Validate that instance can be created from container $this->container->get($className); return $command; } catch (ReflectionException $e) { throw new \InvalidArgumentException("Invalid command method {$className}::{$methodName}: {$e->getMessage()}"); } catch (Throwable $e) { throw new \RuntimeException("Failed to instantiate command class {$className}: {$e->getMessage()}"); } } private function normalizeCommandResult($result): ExitCode { if ($result instanceof ExitCode) { return $result; } if (is_int($result)) { try { return ExitCode::from($result); } catch (\ValueError) { return ExitCode::GENERAL_ERROR; } } if (is_bool($result)) { return $result ? ExitCode::SUCCESS : ExitCode::GENERAL_ERROR; } return ExitCode::SUCCESS; } /** * Execute command with automatic parameter resolution */ private function executeCommandWithReflection( object $instance, ReflectionMethod $method, array $arguments, ConsoleOutputInterface $output ): ExitCode { try { // Create the actual command execution callback $commandExecutor = function (ConsoleInput $input, ConsoleOutputInterface $progressAwareOutput) use ($instance, $method, $arguments) { // Performance tracking: Parameter resolution $parameterStart = microtime(true); // Resolve parameters with framework injection support $resolvedParams = $this->parameterResolver->resolveParameters( $method, $arguments, $input, // Inject ConsoleInput $progressAwareOutput // Inject ConsoleOutput ); $parameterDuration = (microtime(true) - $parameterStart) * 1000; if ($this->performanceCollector) { $this->performanceCollector->recordParameterResolutionTime($method->getDeclaringClass()->getName(), $parameterDuration); } $result = $method->invokeArgs($instance, $resolvedParams); return $this->normalizeCommandResult($result); }; // Wrap execution with ProgressMiddleware $input = new ConsoleInput($arguments, $output); return $this->progressMiddleware->handle($input, $output, $commandExecutor, $method, $instance); } catch (\ArgumentCountError $e) { throw new \InvalidArgumentException( "Invalid number of arguments for command. " . $e->getMessage() ); } catch (\TypeError $e) { throw new \InvalidArgumentException( "Type error in command execution: " . $e->getMessage() ); } } /** * Generate help for a specific command */ public function generateCommandHelp(string $commandName): string { $discoveredAttribute = $this->getDiscoveredAttribute($commandName); $className = $discoveredAttribute->className->getFullyQualified(); $methodName = $discoveredAttribute->methodName?->toString() ?? '__invoke'; try { $reflectionMethod = new ReflectionMethod($className, $methodName); return $this->parameterResolver->generateMethodHelp($reflectionMethod, $commandName); } catch (ReflectionException $e) { return "Command: {$commandName}\nError generating help: {$e->getMessage()}"; } } }