validate(); // Resolve or create required dependencies $reflectionProvider = $this->resolveReflectionProvider(); $fileSystemService = $this->resolveFileSystemService(); // Resolve optional dependencies based on configuration $logger = $config->enablePerformanceTracking ? $this->resolveLogger() : null; $eventDispatcher = $config->enableEventDispatcher ? $this->resolveEventDispatcher() : null; $memoryMonitor = $config->enableMemoryMonitoring ? $this->resolveMemoryMonitor() : null; // Merge configured mappers with defaults $attributeMappers = $this->buildAttributeMappers($config->attributeMappers); $targetInterfaces = $this->buildTargetInterfaces($config->targetInterfaces); // Try to get ExecutionContext from container, or detect it $executionContext = null; if ($this->container->has(ExecutionContext::class)) { $executionContext = $this->container->get(ExecutionContext::class); } else { $executionContext = ExecutionContext::detect(); } return new UnifiedDiscoveryService( pathProvider: $this->pathProvider, cache: $this->cache, clock: $this->clock, reflectionProvider: $reflectionProvider, configuration: $config, attributeMappers: $attributeMappers, targetInterfaces: $targetInterfaces, logger: $logger, eventDispatcher: $eventDispatcher, memoryMonitor: $memoryMonitor, fileSystemService: $fileSystemService, executionContext: $executionContext ); } /** * Create DiscoveryService for development environment */ public function createForDevelopment(array $paths = []): UnifiedDiscoveryService { $config = DiscoveryConfiguration::development(); $scanPaths = ! empty($paths) ? $paths : $this->getDefaultScanPaths(); $config = $config->withPaths($scanPaths); return $this->create($config); } /** * Create DiscoveryService for production environment */ public function createForProduction(array $paths = []): UnifiedDiscoveryService { $config = DiscoveryConfiguration::production(); $scanPaths = ! empty($paths) ? $paths : $this->getDefaultScanPaths(); $config = $config->withPaths($scanPaths); return $this->create($config); } /** * Create DiscoveryService for testing environment */ public function createForTesting(array $paths = []): UnifiedDiscoveryService { $config = DiscoveryConfiguration::testing(); $scanPaths = ! empty($paths) ? $paths : $this->getDefaultScanPaths(); $config = $config->withPaths($scanPaths); return $this->create($config); } /** * Create DiscoveryService with custom configuration */ public function createWithCustomConfig( array $paths = [], bool $useCache = true, array $attributeMappers = [], array $targetInterfaces = [] ): UnifiedDiscoveryService { $config = new DiscoveryConfiguration( paths: $paths, attributeMappers: $attributeMappers, targetInterfaces: $targetInterfaces, useCache: $useCache ); return $this->create($config); } /** * Resolve ReflectionProvider from container or create default */ private function resolveReflectionProvider(): ReflectionProvider { if ($this->container->has(ReflectionProvider::class)) { return $this->container->get(ReflectionProvider::class); } return new CachedReflectionProvider(); } /** * Resolve FileSystemService from container or create default */ private function resolveFileSystemService(): FileSystemService { if ($this->container->has(FileSystemService::class)) { return $this->container->get(FileSystemService::class); } return new FileSystemService(); } /** * Resolve Logger from container if available */ private function resolveLogger(): ?Logger { return $this->container->has(Logger::class) ? $this->container->get(Logger::class) : null; } /** * Resolve EventDispatcher from container if available */ private function resolveEventDispatcher(): ?EventDispatcher { return $this->container->has(EventDispatcher::class) ? $this->container->get(EventDispatcher::class) : null; } /** * Resolve MemoryMonitor from container if available */ private function resolveMemoryMonitor(): ?MemoryMonitor { return $this->container->has(MemoryMonitor::class) ? $this->container->get(MemoryMonitor::class) : null; } /** * Build attribute mappers array with defaults and user-provided mappers */ private function buildAttributeMappers(array $customMappers = []): array { $defaultMappers = [ new RouteMapper(), new EventHandlerMapper(), new \App\Framework\EventBus\EventHandlerMapper(), new QueryHandlerMapper(), new CommandHandlerMapper(), new InitializerMapper(), new McpToolMapper(), new McpResourceMapper(), new ConsoleCommandMapper(), ]; // Merge custom mappers with defaults, allowing override by class name $mappers = []; $mapperClasses = []; // Add default mappers first foreach ($defaultMappers as $mapper) { $className = get_class($mapper); $mappers[] = $mapper; $mapperClasses[] = $className; } // Add custom mappers, replacing defaults if same class foreach ($customMappers as $mapper) { $className = get_class($mapper); $existingIndex = array_search($className, $mapperClasses, true); if ($existingIndex !== false) { // Replace existing mapper $mappers[$existingIndex] = $mapper; } else { // Add new mapper $mappers[] = $mapper; $mapperClasses[] = $className; } } return $mappers; } /** * Build target interfaces array with defaults and user-provided interfaces */ private function buildTargetInterfaces(array $customInterfaces = []): array { $defaultInterfaces = [ AttributeMapper::class, HttpMiddleware::class, DomProcessor::class, StringProcessor::class, Migration::class, ]; // Merge and deduplicate return array_values(array_unique(array_merge($defaultInterfaces, $customInterfaces))); } /** * Create a lightweight factory for specific use cases */ public static function lightweight( PathProvider $pathProvider, Cache $cache, Clock $clock ): self { // Create a minimal container for basic dependencies $container = new DefaultContainer(); return new self($container, $pathProvider, $cache, $clock); } /** * Validate that all required services are available * @return string[] */ public function validateDependencies(DiscoveryConfiguration $config): array { $issues = []; // Check required dependencies if (! $this->container->has(PathProvider::class) && ! isset($this->pathProvider)) { $issues[] = 'PathProvider is required but not available'; } if (! $this->container->has(Cache::class) && ! isset($this->cache)) { $issues[] = 'Cache is required but not available'; } if (! $this->container->has(Clock::class) && ! isset($this->clock)) { $issues[] = 'Clock is required but not available'; } // Check optional dependencies if enabled if ($config->enableEventDispatcher && ! $this->container->has(EventDispatcher::class)) { $issues[] = 'EventDispatcher is enabled but not available in container'; } if ($config->enableMemoryMonitoring && ! $this->container->has(MemoryMonitor::class)) { $issues[] = 'MemoryMonitor is enabled but not available in container'; } if ($config->enablePerformanceTracking && ! $this->container->has(Logger::class)) { $issues[] = 'Logger is needed for performance tracking but not available in container'; } return $issues; } /** * Get default scan paths for discovery * @return string[] */ private function getDefaultScanPaths(): array { $basePath = $this->pathProvider->getBasePath(); return [ $this->pathProvider->getSourcePath(), #$basePath . '/src', #$basePath . '/app', // Support for app/ directory structure ]; } }