diff --git a/src/Framework/DI/Exceptions/CyclicDependencyException.php b/src/Framework/DI/Exceptions/CyclicDependencyException.php index d92862d9..a3770a9d 100644 --- a/src/Framework/DI/Exceptions/CyclicDependencyException.php +++ b/src/Framework/DI/Exceptions/CyclicDependencyException.php @@ -7,6 +7,7 @@ namespace App\Framework\DI\Exceptions; use App\Framework\DI\Container; use App\Framework\DI\InitializerDependencyAnalyzer; use App\Framework\Exception\ExceptionContext; +use Throwable; final class CyclicDependencyException extends ContainerException { @@ -17,10 +18,10 @@ final class CyclicDependencyException extends ContainerException private readonly ?InitializerDependencyAnalyzer $dependencyAnalyzer; public function __construct( - array $dependencyChain, - string $class, - int $code = 0, - ?\Throwable $previous = null, + array $dependencyChain, + string $class, + int $code = 0, + ?Throwable $previous = null, ?InitializerDependencyAnalyzer $dependencyAnalyzer = null ) { // Speichere vollständige Kette @@ -261,21 +262,40 @@ final class CyclicDependencyException extends ContainerException // Verwende Analyzer aus Exception (wenn verfügbar) $analyzer = $this->dependencyAnalyzer ?? new InitializerDependencyAnalyzer(); + // DEBUG: Logge welche Dependencies geprüft werden + error_log(sprintf( + "[CYCLIC_DEBUG] Suche problematische Dependency für Interface '%s' in %d Dependencies: [%s]", + $interface, + count($initializerDependencies), + implode(', ', $initializerDependencies) + )); + // Methode 1: Rekursive Suche - Finde vollständigen Pfad für jede Dependency foreach ($initializerDependencies as $dependency) { // Überspringe Container selbst if ($dependency === Container::class || $dependency === 'App\Framework\DI\Container') { + error_log(sprintf("[CYCLIC_DEBUG] Überspringe Container '%s'", $dependency)); continue; } + error_log(sprintf("[CYCLIC_DEBUG] Prüfe Dependency '%s' auf Pfad zu '%s'", $dependency, $interface)); + $path = $analyzer->findDependencyPathToInterface($dependency, $interface); + if ($path !== null && !empty($path)) { + error_log(sprintf( + "[CYCLIC_DEBUG] ✅ Pfad gefunden für '%s': [%s]", + $dependency, + implode(' → ', $path) + )); // Der erste Eintrag im Pfad ist die Dependency, die das Interface benötigt return [ 'problematicDependency' => $dependency, 'confirmationMethod' => 'recursive_analysis', 'fullPath' => $path, ]; + } else { + error_log(sprintf("[CYCLIC_DEBUG] ❌ Kein Pfad gefunden für '%s'", $dependency)); } } @@ -384,7 +404,7 @@ final class CyclicDependencyException extends ContainerException } return false; - } catch (\Throwable) { + } catch (Throwable) { return false; } } @@ -405,7 +425,7 @@ final class CyclicDependencyException extends ContainerException $reflection = new \ReflectionClass($this->cycleStart); $isInterfaceOrAbstract = $reflection->isAbstract(); } - } catch (\Throwable) { + } catch (Throwable) { // Ignoriere Reflection-Fehler } diff --git a/src/Framework/DI/InitializerDependencyAnalyzer.php b/src/Framework/DI/InitializerDependencyAnalyzer.php index e54eb45b..b56300d8 100644 --- a/src/Framework/DI/InitializerDependencyAnalyzer.php +++ b/src/Framework/DI/InitializerDependencyAnalyzer.php @@ -247,25 +247,46 @@ final readonly class InitializerDependencyAnalyzer array $currentPath = [], int $depth = 0 ): ?array { + // DEBUG: Temporäres Logging + error_log(sprintf( + "[DEPENDENCY_DEBUG] Tiefe %d: Suche Pfad von '%s' zu '%s' (aktueller Pfad: [%s])", + $depth, + $dependencyClass, + $targetInterface, + implode(' → ', $currentPath) + )); + // Max. Rekursionstiefe erreicht if ($depth >= self::MAX_RECURSION_DEPTH) { + error_log(sprintf("[DEPENDENCY_DEBUG] Max. Tiefe erreicht für '%s'", $dependencyClass)); return null; } // Cycle-Detection: Vermeide Endlosschleifen if (in_array($dependencyClass, $visited, true)) { + error_log(sprintf("[DEPENDENCY_DEBUG] Cycle erkannt für '%s' (bereits besucht)", $dependencyClass)); return null; } // Prüfe ob diese Klasse direkt das Interface benötigt if ($this->dependencyNeedsInterface($dependencyClass, $targetInterface)) { - return array_merge($currentPath, [$dependencyClass, $targetInterface]); + $path = array_merge($currentPath, [$dependencyClass, $targetInterface]); + error_log(sprintf("[DEPENDENCY_DEBUG] ✅ Pfad gefunden: [%s]", implode(' → ', $path))); + return $path; } // Rekursiv: Analysiere Dependencies dieser Klasse $dependencies = $this->getClassDependencies($dependencyClass); + error_log(sprintf( + "[DEPENDENCY_DEBUG] '%s' hat %d Dependencies: [%s]", + $dependencyClass, + count($dependencies), + implode(', ', $dependencies) + )); + if (empty($dependencies)) { + error_log(sprintf("[DEPENDENCY_DEBUG] Keine Dependencies für '%s'", $dependencyClass)); return null; } @@ -275,9 +296,16 @@ final readonly class InitializerDependencyAnalyzer foreach ($dependencies as $subDependency) { // Überspringe Container selbst (würde alle Dependencies auflisten) if ($subDependency === Container::class || $subDependency === 'App\Framework\DI\Container') { + error_log(sprintf("[DEPENDENCY_DEBUG] Überspringe Container '%s'", $subDependency)); continue; } + error_log(sprintf( + "[DEPENDENCY_DEBUG] Prüfe Sub-Dependency '%s' (Tiefe %d)", + $subDependency, + $depth + 1 + )); + $path = $this->findDependencyPathToInterface( $subDependency, $targetInterface, @@ -287,10 +315,12 @@ final readonly class InitializerDependencyAnalyzer ); if ($path !== null) { + error_log(sprintf("[DEPENDENCY_DEBUG] ✅ Pfad über '%s' gefunden!", $subDependency)); return $path; } } + error_log(sprintf("[DEPENDENCY_DEBUG] ❌ Kein Pfad gefunden für '%s'", $dependencyClass)); return null; } @@ -364,6 +394,7 @@ final readonly class InitializerDependencyAnalyzer { try { if (!class_exists($className) && !interface_exists($className)) { + error_log(sprintf("[DEPENDENCY_DEBUG] Klasse/Interface existiert nicht: '%s'", $className)); return []; } @@ -371,11 +402,14 @@ final readonly class InitializerDependencyAnalyzer // Wenn es ein Interface ist, versuche die Implementierung zu finden if ($reflection->isInterface()) { + error_log(sprintf("[DEPENDENCY_DEBUG] '%s' ist Interface, suche Implementierung...", $className)); // Suche nach bekannten Implementierungen (basierend auf Namenskonvention) $implClass = $this->findInterfaceImplementation($className); if ($implClass !== null && class_exists($implClass)) { + error_log(sprintf("[DEPENDENCY_DEBUG] ✅ Implementierung gefunden: '%s' → '%s'", $className, $implClass)); $reflection = new \ReflectionClass($implClass); } else { + error_log(sprintf("[DEPENDENCY_DEBUG] ❌ Keine Implementierung für Interface '%s' gefunden", $className)); // Kann keine Dependencies für Interfaces ohne Implementierung finden return []; } @@ -530,31 +564,50 @@ final readonly class InitializerDependencyAnalyzer */ private function findInterfaceImplementation(string $interface): ?string { + error_log(sprintf("[DEPENDENCY_DEBUG] findInterfaceImplementation: Suche Implementierung für '%s'", $interface)); + // 1. Versuche über Container-Bindings (falls Container verfügbar) if ($this->container !== null) { try { + error_log(sprintf("[DEPENDENCY_DEBUG] Container verfügbar, prüfe has('%s')", $interface)); // Prüfe ob Interface gebunden ist if ($this->container->has($interface)) { + error_log(sprintf("[DEPENDENCY_DEBUG] Interface '%s' hat Binding, hole Binding...", $interface)); $binding = $this->getBindingForInterface($interface); if ($binding !== null) { + error_log(sprintf("[DEPENDENCY_DEBUG] Binding gefunden: %s (Typ: %s)", + is_string($binding) ? $binding : (is_object($binding) ? $binding::class : 'callable'), + gettype($binding) + )); + // Wenn Binding ein String ist (Klassenname), verwende diesen if (is_string($binding) && class_exists($binding)) { + error_log(sprintf("[DEPENDENCY_DEBUG] ✅ Verwende String-Binding: '%s'", $binding)); return $binding; } // Wenn Binding ein Objekt ist, verwende dessen Klasse if (is_object($binding)) { + error_log(sprintf("[DEPENDENCY_DEBUG] ✅ Verwende Objekt-Binding: '%s'", $binding::class)); return $binding::class; } // Wenn Binding ein Callable ist, können wir es nicht direkt auflösen // aber wir können versuchen, es zu instanziieren (wenn kein Zyklus) // Das ist riskant, also überspringen wir es für jetzt + error_log(sprintf("[DEPENDENCY_DEBUG] ⚠️ Binding ist Callable, kann nicht direkt verwendet werden")); + } else { + error_log(sprintf("[DEPENDENCY_DEBUG] ❌ Kein Binding gefunden für '%s'", $interface)); } + } else { + error_log(sprintf("[DEPENDENCY_DEBUG] Container->has('%s') = false", $interface)); } - } catch (\Throwable) { + } catch (\Throwable $e) { + error_log(sprintf("[DEPENDENCY_DEBUG] ❌ Container-Zugriff fehlgeschlagen: %s", $e->getMessage())); // Container-Zugriff fehlgeschlagen - ignoriere und versuche Fallback } + } else { + error_log(sprintf("[DEPENDENCY_DEBUG] ❌ Container nicht verfügbar")); } // 2. Versuche Namenskonvention: Interface -> DefaultInterfaceName