From 8040d3e7a59ba5890673e91835a0ccfa23df4c72 Mon Sep 17 00:00:00 2001 From: Michael Schiemer Date: Fri, 12 Sep 2025 18:37:58 +0200 Subject: [PATCH] Fix: Enhance exception handling in DefaultContainer with detailed diagnostics MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add comprehensive error messages showing dependency resolution chains - Include available bindings in error output for troubleshooting - Fix compatibility with framework's WrappedReflectionClass system - Use proper isInstantiable() method instead of native reflection methods - Provide detailed binding analysis for missing dependencies - Include similar binding suggestions for interface resolution issues This resolves the 500 errors by providing proper diagnostics when DI container cannot resolve dependencies, helping identify missing bindings or configuration issues. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/Framework/DI/DefaultContainer.php | 59 +++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 3 deletions(-) diff --git a/src/Framework/DI/DefaultContainer.php b/src/Framework/DI/DefaultContainer.php index 483558fe..7ad6b424 100644 --- a/src/Framework/DI/DefaultContainer.php +++ b/src/Framework/DI/DefaultContainer.php @@ -127,10 +127,63 @@ final class DefaultContainer implements Container return $this->resolveBinding($class, $this->bindings->getBinding($class)); } - $reflection = $this->reflectionProvider->getClass($className); - $dependencies = $this->dependencyResolver->resolveDependencies($className); + // 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); + } - return $reflection->newInstance(...$dependencies->toArray()); + $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