dependencyResolver = new DependencyResolver($this->reflectionProvider, $this); $this->singletonDetector = new SingletonDetector($this->reflectionProvider, $this->instances); $this->lazyInstantiator = new LazyInstantiator( $this->reflectionProvider, $this->createInstance(...) ); $this->invoker = new MethodInvoker($this, $this->reflectionProvider); $this->introspector = new ContainerIntrospector( $this, $this->instances, $this->bindings, $this->reflectionProvider, fn (): array => $this->resolving ); $this->metrics = new FrameworkMetricsCollector(); $this->dependencyAnalyzer = new InitializerDependencyAnalyzer($this); $this->registerSelf(); $this->instance(ReflectionProvider::class, $this->reflectionProvider); } public function bind(ClassName|Stringable|string $abstract, callable|string|object $concrete): void { $abstract = (string) $abstract; $this->bindings->bind($abstract, $concrete); // Only clear caches for valid class names, skip string keys like 'filesystem.storage.local' if (class_exists($abstract) || interface_exists($abstract)) { $this->clearCaches(ClassName::create($abstract)); } } public function singleton(ClassName|Stringable|string $abstract, callable|string|object $concrete): void { $abstract = (string) $abstract; $this->bind($abstract, $concrete); $this->instances->markAsSingleton($abstract); } public function instance(ClassName|Stringable|string $abstract, object $instance): void { $this->instances->setInstance($abstract, $instance); } /** * @template T of object * @param class-string|ClassName|Stringable $class * @return T */ public function get(ClassName|Stringable|string $class): object { $className = (string) $class; // Bereits instanziierte Objekte zurückgeben $singleton = $this->instances->getSingleton($className); if ($singleton !== null) { return $singleton; } if ($this->instances->hasInstance($className)) { return $this->instances->getInstance($className); } // Lazy Loading versuchen if ($this->lazyInstantiator->canUseLazyLoading($className, $this->instances)) { try { return $this->lazyInstantiator->createLazyInstance($className); } catch (LazyLoadingException $e) { // Log LazyLoading-Fehler und versuche Fallback (Logger ist immer verfügbar) $this->get(Logger::class)->warning( "Lazy loading failed for {$className}, falling back to normal instantiation", LogContext::withException($e) ); // Fallback: Versuche normale Instanziierung try { return $this->createInstance($className); } catch (\Throwable $fallbackException) { // Wenn Fallback auch fehlschlägt, ursprüngliche LazyLoadingException werfen throw $e; } } catch (\Throwable $e) { // Andere Exceptions direkt weiterwerfen throw $e; } } return $this->createInstance($className); } /** * @template T of object * @param class-string $class * @return T */ private function createInstance(string $class): object { if (in_array($class, $this->resolving, true)) { throw new CyclicDependencyException( dependencyChain: $this->resolving, class: $class, code: 0, previous: null, dependencyAnalyzer: $this->dependencyAnalyzer ); } $this->resolving[] = $class; try { $instance = $this->buildInstance($class); // Only check singleton attribute for actual classes if ((class_exists($class) || interface_exists($class)) && $this->singletonDetector->isSingleton(ClassName::create($class))) { $this->instances->setSingleton($class, $instance); } else { $this->instances->setInstance($class, $instance); } return $instance; } catch (Throwable $e) { // Track resolution failures $this->metrics->increment('container.resolve.failures'); throw $e; } finally { array_pop($this->resolving); } } private function buildInstance(string $class): object { if ($this->bindings->hasBinding($class)) { return $this->resolveBinding($class, $this->bindings->getBinding($class)); } // For string keys without bindings, throw immediately if (!class_exists($class) && !interface_exists($class)) { throw new ClassNotResolvableException( class: $class, availableBindings: array_keys($this->bindings->getAllBindings()) ); } $className = ClassName::create($class); // Enhanced diagnostics for missing bindings try { $reflection = $this->reflectionProvider->getClass($className); // Check if class is instantiable using the framework's method if (! $reflection->isInstantiable()) { // Proaktive Suche nach Initializern für Interfaces if ($reflection->isInterface() && $this->tryFindAndRegisterInitializer($class)) { // Initializer gefunden und registriert - versuche erneut zu resolven return $this->get($class); } $this->throwDetailedBindingException($class); } $dependencies = $this->dependencyResolver->resolveDependencies($className); return $reflection->newInstance(...$dependencies->toArray()); } catch (ContainerException $e) { // If it's already a ContainerException, just re-throw throw $e; } catch (\RuntimeException|\ReflectionException $e) { // Wrap with binding information throw ClassResolutionException::fromRuntimeException( class: $class, exception: $e, availableBindings: array_keys($this->bindings->getAllBindings()), dependencyChain: $this->resolving ); } } private function throwDetailedBindingException(string $class): never { $availableBindings = array_keys($this->bindings->getAllBindings()); // Bestimme den Typ der Klasse mit Reflection $className = ClassName::create($class); $isInterface = false; $isAbstract = false; $isTrait = false; try { $reflection = $this->reflectionProvider->getClass($className); $isInterface = $reflection->isInterface(); $isAbstract = $reflection->isAbstract(); $isTrait = $reflection->isTrait(); } catch (\Throwable $e) { // Fallback zu einfachen Prüfungen wenn Reflection fehlschlägt $isInterface = interface_exists($class); // Für Abstract und Trait können wir nicht so einfach prüfen, aber wir haben bereits // festgestellt dass die Klasse nicht instanziierbar ist } // Try to get DiscoveryRegistry from container and include discovered initializers $discoveredInitializers = []; $matchingInitializers = []; $suggestedInitializer = null; $failedInitializer = null; if ($this->has(DiscoveryRegistry::class)) { try { $discoveryRegistry = $this->get(DiscoveryRegistry::class); $initializerResults = $discoveryRegistry->attributes->get(Initializer::class); if (! empty($initializerResults)) { $discoveredInitializers = array_map( fn($attr) => $attr->className->getFullyQualified(), $initializerResults ); // Spezielle Behandlung für Interfaces: Suche nach Initializern die dieses Interface zurückgeben if ($isInterface) { foreach ($initializerResults as $initializer) { $returnType = $initializer->additionalData['return'] ?? null; if ($returnType === $class) { $matchingInitializers[] = $initializer->className->getFullyQualified(); } } // Vorschlag basierend auf Interface-Name (z.B. ComponentRegistryInterface -> ComponentRegistryInitializer) $interfaceName = basename(str_replace('\\', '/', $class)); $suggestedName = str_replace('Interface', '', $interfaceName) . 'Initializer'; foreach ($discoveredInitializers as $initializer) { if (str_contains($initializer, $suggestedName)) { $suggestedInitializer = $initializer; break; } } } } } catch (\Throwable $e) { // Silently ignore errors when trying to access DiscoveryRegistry to avoid masking the original error } } // Prüfe ob ein Initializer für dieses Interface fehlgeschlagen ist if ($this->has(FailedInitializerRegistry::class)) { try { $failedRegistry = $this->get(FailedInitializerRegistry::class); if ($failedRegistry->hasFailedInitializer($class)) { $failedInitializer = $failedRegistry->getFailedInitializer($class); } } catch (\Throwable $e) { // Silently ignore errors when trying to access FailedInitializerRegistry } } throw ClassNotInstantiable::fromContainerContext( class: $class, dependencyChain: $this->resolving, availableBindings: $availableBindings, discoveredInitializers: $discoveredInitializers, matchingInitializers: $matchingInitializers, suggestedInitializer: $suggestedInitializer, failedInitializer: $failedInitializer, isInterface: $isInterface, isAbstract: $isAbstract, isTrait: $isTrait ); } private function resolveBinding(string $class, callable|string|object $concrete): object { try { return match (true) { is_callable($concrete) => $concrete($this), is_string($concrete) => $this->get($concrete), /* @var object $concrete */ default => $concrete }; } catch (ContainerException $e) { // Re-throw ContainerExceptions as-is (they already have proper context) throw $e; } catch (\Throwable $e) { // Determine binding type for better error messages $bindingType = match (true) { is_callable($concrete) => 'callable', is_string($concrete) => 'string', default => 'object' }; // Für callable bindings: Prüfe ob ein Initializer fehlgeschlagen ist $failedInitializer = null; $matchingInitializer = null; if ($bindingType === 'callable') { // Prüfe ob ein fehlgeschlagener Initializer für diese Klasse existiert if ($this->has(FailedInitializerRegistry::class)) { try { $failedRegistry = $this->get(FailedInitializerRegistry::class); if ($failedRegistry->hasFailedInitializer($class)) { $failedInitializer = $failedRegistry->getFailedInitializer($class); } } catch (\Throwable $registryError) { // Silently ignore errors when trying to access FailedInitializerRegistry } } // Suche auch in DiscoveryRegistry nach Initializern die diese Klasse zurückgeben if ($failedInitializer === null && $this->has(DiscoveryRegistry::class)) { try { $discoveryRegistry = $this->get(DiscoveryRegistry::class); $initializerResults = $discoveryRegistry->attributes->get(Initializer::class); if (! empty($initializerResults)) { foreach ($initializerResults as $initializer) { $returnType = $initializer->additionalData['return'] ?? null; if ($returnType === $class) { $matchingInitializer = $initializer->className->getFullyQualified(); break; } } } } catch (\Throwable $registryError) { // Silently ignore errors when trying to access DiscoveryRegistry } } } throw ClassResolutionException::fromBindingResolution( class: $class, previous: $e, availableBindings: array_keys($this->bindings->getAllBindings()), dependencyChain: $this->resolving, bindingType: $bindingType, failedInitializer: $failedInitializer, matchingInitializer: $matchingInitializer ); } } /** @param class-string $class */ public function has(ClassName|Stringable|string $class): bool { $class = (string) $class; return $this->instances->hasSingleton($class) || $this->instances->hasInstance($class) || $this->bindings->hasBinding($class) || $this->canAutoWire($class); } /** * Prüft ob eine Klasse automatisch instanziiert werden kann (auto-wiring) * @param string $class Klassenname */ private function canAutoWire(string $class): bool { if (empty($class)) { return false; } $className = ClassName::create($class); if (! $className->exists()) { return false; } try { return $this->reflectionProvider->getClass($className)->isInstantiable(); } catch (\Throwable $e) { return false; } } /** @param class-string $class */ public function forget(ClassName|Stringable|string $class): void { $class = (string) $class; $this->instances->forget($class); $this->bindings->forget($class); $this->clearCaches(ClassName::create($class)); } public function flush(): void { $this->instances->flush(); $this->bindings->flush(); $this->reflectionProvider->flush(); $this->dependencyResolver->flushCache(); $this->lazyInstantiator->flushFactories(); // Container selbst wieder registrieren $this->registerSelf(); } public function getRegisteredServices(): array { return array_merge( $this->instances->getAllRegistered(), $this->bindings->getAllBindings() ); } /** * Get all registered service IDs (for debugging/admin) * @return array */ public function getServiceIds(): array { return array_keys($this->getRegisteredServices()); } private function clearCaches(ClassName $className): void { $this->reflectionProvider->forget($className); $this->dependencyResolver->clearCache($className); $this->lazyInstantiator->forgetFactory($className->getFullyQualified()); } private function registerSelf(): void { $this->instances->setSingleton(self::class, $this); $this->instances->setSingleton(DefaultContainer::class, $this); $this->instances->setSingleton(Container::class, $this); } /** * Lazy-getter für ProactiveInitializerFinder */ private function getProactiveFinder(): ProactiveInitializerFinder { // Erstelle Finder nur wenn benötigt $discoveryRegistry = $this->has(DiscoveryRegistry::class) ? $this->get(DiscoveryRegistry::class) : null; if ($discoveryRegistry === null) { // Fallback: Leere Registry erstellen $discoveryRegistry = \App\Framework\Discovery\Results\DiscoveryRegistry::empty(); } $fileScanner = $this->has(\App\Framework\Filesystem\FileScanner::class) ? $this->get(\App\Framework\Filesystem\FileScanner::class) : new \App\Framework\Filesystem\FileScanner(null, null, new \App\Framework\Filesystem\FileSystemService()); $fileSystemService = new \App\Framework\Filesystem\FileSystemService(); $classExtractor = new \App\Framework\Discovery\Processing\ClassExtractor($fileSystemService); $pathProvider = $this->has(\App\Framework\Core\PathProvider::class) ? $this->get(\App\Framework\Core\PathProvider::class) : new \App\Framework\Core\PathProvider(getcwd() ?: '.'); return new ProactiveInitializerFinder( reflectionProvider: $this->reflectionProvider, discoveryRegistry: $discoveryRegistry, fileScanner: $fileScanner, classExtractor: $classExtractor, pathProvider: $pathProvider ); } /** * Versucht proaktiv einen Initializer für ein Interface zu finden und zu registrieren * * @param string $interface Interface-Klasse * @return bool True wenn Initializer gefunden und registriert wurde, false sonst */ private function tryFindAndRegisterInitializer(string $interface): bool { try { $finder = $this->getProactiveFinder(); $initializerInfo = $finder->findInitializerForInterface($interface); if ($initializerInfo === null) { return false; } // Registriere Initializer als lazy binding $initializerClass = $initializerInfo->initializerClass->getFullyQualified(); $methodName = $initializerInfo->methodName->toString(); $this->singleton($interface, function (Container $container) use ($initializerClass, $methodName) { $instance = $container->get($initializerClass); return $container->invoker->invoke($initializerClass, $methodName); }); // Gefundenen Initializer in Discovery Cache nachtragen $this->updateDiscoveryCache($initializerInfo); return true; } catch (\Throwable $e) { // Silently ignore errors during proactive search return false; } } /** * Aktualisiert den Discovery Cache mit einem proaktiv gefundenen Initializer */ private function updateDiscoveryCache(InitializerInfo $initializerInfo): void { try { $updater = $this->getCacheUpdater(); if ($updater === null) { return; } // Versuche zuerst Registry aus Container zu laden $registry = $this->has(DiscoveryRegistry::class) ? $this->get(DiscoveryRegistry::class) : null; $updater->updateCache($initializerInfo, $registry); } catch (\Throwable $e) { // Silently ignore errors during cache update // Initializer funktioniert trotzdem, nur Cache-Update schlägt fehl } } /** * Holt oder erstellt einen InitializerCacheUpdater */ private function getCacheUpdater(): ?InitializerCacheUpdater { try { // Prüfe ob alle benötigten Abhängigkeiten verfügbar sind if (!$this->has(\App\Framework\Discovery\Storage\DiscoveryCacheManager::class)) { // Versuche DiscoveryCacheManager zu erstellen if (!$this->has(\App\Framework\Cache\Cache::class) || !$this->has(\App\Framework\DateTime\Clock::class) || !$this->has(\App\Framework\Filesystem\FileSystemService::class)) { return null; } $cache = $this->get(\App\Framework\Cache\Cache::class); $clock = $this->get(\App\Framework\DateTime\Clock::class); $fileSystemService = $this->get(\App\Framework\Filesystem\FileSystemService::class); $cacheManager = new \App\Framework\Discovery\Storage\DiscoveryCacheManager( cache: $cache, clock: $clock, fileSystemService: $fileSystemService ); } else { $cacheManager = $this->get(\App\Framework\Discovery\Storage\DiscoveryCacheManager::class); } $pathProvider = $this->has(\App\Framework\Core\PathProvider::class) ? $this->get(\App\Framework\Core\PathProvider::class) : new \App\Framework\Core\PathProvider(getcwd() ?: '.'); $clock = $this->has(\App\Framework\DateTime\Clock::class) ? $this->get(\App\Framework\DateTime\Clock::class) : new \App\Framework\DateTime\SystemClock(); return new InitializerCacheUpdater( reflectionProvider: $this->reflectionProvider, cacheManager: $cacheManager, pathProvider: $pathProvider, clock: $clock ); } catch (\Throwable $e) { return null; } } }