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->registerSelf(); $this->instance(ReflectionProvider::class, $this->reflectionProvider); } public function bind(string $abstract, callable|string|object $concrete): void { $this->bindings->bind($abstract, $concrete); $this->clearCaches(ClassName::create($abstract)); } public function singleton(string $abstract, callable|string|object $concrete): void { $this->bind($abstract, $concrete); $this->instances->markAsSingleton($abstract); } public function instance(string $abstract, object $instance): void { $this->instances->setInstance($abstract, $instance); } /** * @inheritDoc */ public function get(string|ClassName $class): object { $className = $class instanceof ClassName ? $class->toString() : $class; // Bereits instanziierte Objekte zurückgeben if ($this->instances->hasSingleton($className)) { $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 (Throwable $e) { if (! $e instanceof LazyLoadingException) { 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 ); } $this->resolving[] = $class; try { $instance = $this->buildInstance($class); if ($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 { $className = ClassName::create($class); if ($this->bindings->hasBinding($class)) { return $this->resolveBinding($class, $this->bindings->getBinding($class)); } // Enhanced diagnostics for missing bindings try { $reflection = $this->reflectionProvider->getClass($className); // Check if class is instantiable using framework's method if (! $reflection->isInstantiable()) { $this->throwDetailedBindingException($class, $reflection); } $dependencies = $this->dependencyResolver->resolveDependencies($className); return $reflection->newInstance(...$dependencies->toArray()); } catch (\RuntimeException $e) { // If it's already our detailed exception, just re-throw if (str_contains($e->getMessage(), 'Dependency resolution chain:')) { throw $e; } // Otherwise, wrap with binding information throw new \RuntimeException( "Cannot resolve class '{$class}': {$e->getMessage()}. " . "Available bindings: " . implode(', ', array_keys($this->bindings->getAllBindings())) . ". Dependency chain: " . implode(' -> ', $this->resolving), 0, $e ); } catch (\ReflectionException $e) { throw new \RuntimeException( "Cannot resolve class '{$class}': {$e->getMessage()}. " . "Available bindings: " . implode(', ', array_keys($this->bindings->getAllBindings())) . ". Dependency chain: " . implode(' -> ', $this->resolving), 0, $e ); } } private function throwDetailedBindingException(string $class, $reflection): never { $availableBindings = array_keys($this->bindings->getAllBindings()); $dependencyChain = implode(' -> ', $this->resolving); // Look for similar interface bindings $similarBindings = array_filter($availableBindings, function ($binding) use ($class) { return str_contains($binding, basename(str_replace('\\', '/', $class))); }); $message = "Cannot instantiate class '{$class}': class is not instantiable (interface, abstract class, or trait).\n" . "Dependency resolution chain: {$dependencyChain}\n" . "Total available bindings: " . count($availableBindings) . "\n"; if (! empty($similarBindings)) { $message .= "Similar bindings found: " . implode(', ', $similarBindings) . "\n"; } $message .= "All bindings: " . implode(', ', $availableBindings); throw new \RuntimeException($message); } private function resolveBinding(string $class, callable|string|object $concrete): object { return match (true) { is_callable($concrete) => $concrete($this), is_string($concrete) => $this->get($concrete), default => $concrete }; } /** @param class-string $class */ public function has(string $class): bool { return $this->instances->hasSingleton($class) || $this->instances->hasInstance($class) || $this->bindings->hasBinding($class) || (! empty($class) && ClassName::create($class)->exists() && $this->reflectionProvider->getClass(ClassName::create($class))->isInstantiable()); } /** @param class-string $class */ public function forget(string $class): void { $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); } }