container->get(PathProvider::class); $cache = $this->container->get(Cache::class); // Get DiscoveryConfig if available, otherwise use defaults $discoveryConfig = null; if ($this->container->has(DiscoveryConfig::class)) { $discoveryConfig = $this->container->get(DiscoveryConfig::class); } // Context-spezifische Discovery mit separaten Caches $currentContext = ExecutionContext::detect(); $contextString = $currentContext->getType()->value; // Debug logging via framework Logger $this->logger->debug("Context detected = {$contextString}", LogContext::withData([ 'source_path' => $pathProvider->getSourcePath() ])); // Direkter Cache-Check mit expliziter toArray/fromArray Serialisierung $defaultPaths = [$pathProvider->getSourcePath()]; $cacheKey = DiscoveryCacheIdentifiers::fullDiscoveryKey($defaultPaths, $contextString); $this->logger->debug("Cache key = {$cacheKey->toString()}"); $cachedItem = $cache->get($cacheKey); $this->logger->debug("Cache hit = " . ($cachedItem->isHit ? 'YES' : 'NO')); if ($cachedItem->isHit) { $this->logger->debug("Loading from cache..."); // Ensure DiscoveryRegistry class is loaded before attempting deserialization if (! class_exists(DiscoveryRegistry::class, true)) { $cachedRegistry = null; } else { // Versuche die gecachten Daten zu laden $cachedRegistry = null; try { // Skip incomplete classes - they indicate autoloader issues if (is_object($cachedItem->value) && get_class($cachedItem->value) === '__PHP_Incomplete_Class') { $cachedRegistry = null; } elseif ($cachedItem->value instanceof DiscoveryRegistry) { $cachedRegistry = $cachedItem->value; } elseif (is_array($cachedItem->value)) { $cachedRegistry = DiscoveryRegistry::fromArray($cachedItem->value); } elseif (is_string($cachedItem->value)) { $cachedRegistry = DiscoveryRegistry::fromArray(json_decode($cachedItem->value, true, 512, JSON_THROW_ON_ERROR)); } else { $cachedRegistry = null; } } catch (\Throwable $e) { $cachedRegistry = null; } } if ($cachedRegistry !== null && ! $cachedRegistry->isEmpty()) { $routeCount = count($cachedRegistry->attributes->get(\App\Framework\Attributes\Route::class)); $this->logger->debug("Cached registry loaded - Route count: {$routeCount}"); $this->container->singleton(DiscoveryRegistry::class, $cachedRegistry); // Process DefaultImplementation attributes first (before Initializers) $defaultImplProcessor = $this->container->get(\App\Framework\DI\DefaultImplementationProcessor::class); $defaultImplProcessor->process($cachedRegistry); // Initializer-Verarbeitung für gecachte Registry $initializerProcessor = $this->container->get(InitializerProcessor::class); $initializerProcessor->processInitializers($cachedRegistry); return $cachedRegistry; } } // Fallback: Vollständige Discovery durchführen $this->logger->debug("Performing fresh discovery..."); $results = $this->performBootstrap($pathProvider, $cache, $discoveryConfig); $this->logger->debug("Discovery completed", LogContext::withData([ 'is_empty' => $results->isEmpty() ])); // Nach der Discovery explizit in unserem eigenen Cache-Format speichern $consoleCommandCount = count($results->attributes->get(\App\Framework\Console\ConsoleCommand::class)); $routeCount = count($results->attributes->get(\App\Framework\Attributes\Route::class)); $initializerCount = count($results->attributes->get(\App\Framework\DI\Initializer::class)); $this->logger->debug("Discovery results", LogContext::withData([ 'routes' => $routeCount, 'console_commands' => $consoleCommandCount, 'initializers' => $initializerCount ])); // Only cache if we found meaningful results // An empty discovery likely indicates initialization timing issues if (! $results->isEmpty() && $consoleCommandCount > 0) { $arrayData = $results->toArray(); $cacheItem = CacheItem::forSet( key: $cacheKey, value: $arrayData, ttl: Duration::fromHours(1) ); $cache->set($cacheItem); } return $results; } /** * Führt den Discovery-Prozess durch und verarbeitet die Ergebnisse */ public function performBootstrap(PathProvider $pathProvider, Cache $cache, ?DiscoveryConfig $config): DiscoveryRegistry { // Context-spezifische Discovery $currentContext = ExecutionContext::detect(); $discoveryService = $this->createDiscoveryService($pathProvider, $cache, $config, $currentContext); // Discovery durchführen $results = $discoveryService->discover(); // Ergebnisse im Container registrieren $this->container->singleton(DiscoveryRegistry::class, $results); $this->container->instance(UnifiedDiscoveryService::class, $discoveryService); // Process DefaultImplementation attributes first (before Initializers, as they may depend on them) $defaultImplProcessor = $this->container->get(\App\Framework\DI\DefaultImplementationProcessor::class); $defaultImplProcessor->process($results); // Initializer-Verarbeitung mit dedizierter Klasse $initializerProcessor = $this->container->get(InitializerProcessor::class); $initializerProcessor->processInitializers($results); return $results; } /** * Creates the modern discovery service with enhanced features using the new factory */ private function createDiscoveryService( PathProvider $pathProvider, Cache $cache, ?DiscoveryConfig $config, ?ExecutionContext $context = null ): UnifiedDiscoveryService { // Create factory $factory = new DiscoveryServiceFactory( $this->container, $pathProvider, $cache, $this->clock ); // Determine environment-based factory method $envType = 'production'; // Default fallback if ($this->container->has(AppConfig::class)) { $appConfig = $this->container->get(AppConfig::class); $envType = $appConfig->environment; } elseif ($this->container->has(TypedConfiguration::class)) { $typedConfig = $this->container->get(TypedConfiguration::class); $appConfigFromTyped = $typedConfig->appConfig(); $envType = is_string($appConfigFromTyped->environment) ? $appConfigFromTyped->environment : $appConfigFromTyped->environment->value; } // Use factory methods which include default paths // For console/CLI contexts, always use development configuration // to ensure consistent discovery behavior across CLI environments $currentContext = $context?->getType(); return match (true) { // CLI contexts (console, cli-script, test) should use development config // to ensure consistent command discovery $currentContext?->value === 'console' => $factory->createForDevelopment(), $currentContext?->value === 'cli-script' => $factory->createForDevelopment(), $currentContext?->value === 'test' => $factory->createForDevelopment(), // Environment-based fallback for non-CLI contexts $envType === 'development' => $factory->createForDevelopment(), $envType === 'testing' => $factory->createForDevelopment(), // Use development config for testing too default => $factory->createForProduction() }; } /** * Führt einen inkrementellen Discovery-Scan durch und verarbeitet die Ergebnisse * mit context-aware Initializer-Verarbeitung */ public function incrementalBootstrap(): DiscoveryRegistry { if (! $this->container->has(UnifiedDiscoveryService::class)) { // Fallback auf vollständigen Bootstrap return $this->bootstrap(); } $discoveryService = $this->container->get(UnifiedDiscoveryService::class); $results = $discoveryService->incrementalDiscover(); // Aktualisiere Container $this->container->instance(DiscoveryRegistry::class, $results); // Re-process initializers for current context $initializerProcessor = $this->container->get(InitializerProcessor::class); $initializerProcessor->processInitializers($results); return $results; } /** * Hilfsmethode um zu prüfen, ob Discovery erforderlich ist */ public function isDiscoveryRequired(): bool { if (! $this->container->has(UnifiedDiscoveryService::class)) { return true; } $discoveryService = $this->container->get(UnifiedDiscoveryService::class); return $discoveryService->shouldRescan(); } }