fullChain = array_merge($dependencyChain, [$class]); // Finde wo der Zyklus beginnt (erste Wiederholung in der Kette) $cycleStartIndex = array_search($class, $dependencyChain, true); if ($cycleStartIndex !== false) { // Extrahiere nur den Zyklus-Teil $cycle = array_slice($dependencyChain, $cycleStartIndex); $cycle[] = $class; // Schließe den Zyklus $cycleStart = $dependencyChain[$cycleStartIndex]; // Speichere Teil vor dem Zyklus $this->chainBeforeCycle = array_slice($dependencyChain, 0, $cycleStartIndex); } else { // Fallback: Verwende die gesamte Kette $cycle = array_merge($dependencyChain, [$class]); $cycleStart = $dependencyChain[0] ?? $class; $this->chainBeforeCycle = []; } $this->cycle = $cycle; $this->cycleStart = $cycleStart; $context = ExceptionContext::forOperation('dependency_resolution', 'DI') ->withData([ 'dependencyChain' => $dependencyChain, 'class' => $class, 'cycle' => $this->cycle, 'cycleStart' => $this->cycleStart, ]); $message = $this->buildMessage(); // ContainerException erwartet message als ersten Parameter und optional context parent::__construct( message: $message, context: $context, code: $code, previous: $previous ); } private function buildMessage(): string { $cycleStr = implode(' → ', $this->cycle); $fullChain = $this->fullChain; $requestedClass = end($fullChain); // Prüfe ob Initializer-Zyklus vorliegt $initializerInfo = $this->detectInitializerCycle(); $message = "🔄 Zyklische Abhängigkeit entdeckt:\n\n"; // Zeige Kontext: Wer versucht was zu erstellen $message .= "❌ Problem: Beim Versuch, '{$requestedClass}' zu erstellen,\n"; $message .= " wurde eine zyklische Abhängigkeit entdeckt.\n\n"; // Zeige vollständige Abhängigkeitskette (wenn vorhanden) if (!empty($this->chainBeforeCycle)) { $beforeCycleStr = implode(' → ', $this->chainBeforeCycle); $message .= "📋 Abhängigkeitskette:\n"; $message .= " {$beforeCycleStr} → [ZYKLUS]\n\n"; } // Zeige den Zyklus selbst $message .= "🔄 Zyklus:\n"; $message .= " {$cycleStr}\n"; $message .= " ↑─────────────────────┘\n"; $message .= " Der Zyklus beginnt hier bei '{$this->cycleStart}'\n\n"; // Spezifische Hinweise für Initializer-Zyklen if ($initializerInfo['isInitializerCycle']) { $message .= "⚠️ Initializer-Zyklus erkannt!\n\n"; $message .= " Der Initializer '{$initializerInfo['initializerClass']}' benötigt\n"; $message .= " das Interface/abstrakte Klasse '{$this->cycleStart}',\n"; $message .= " welches wiederum den Initializer benötigt.\n\n"; $message .= "🔧 Spezifische Lösung für Initializer-Zyklen:\n"; $message .= " • Initializer sollte das Interface NICHT im Constructor benötigen\n"; $message .= " • Verwende Container als Parameter und hole das Interface erst im __invoke()\n"; $message .= " • Oder: Refactoriere den Initializer, sodass er keine zirkuläre Abhängigkeit hat\n"; $message .= " • Prüfe, ob der Initializer wirklich alle Dependencies braucht\n\n"; } // Füge allgemeine hilfreiche Hinweise hinzu $message .= "💡 Allgemeine Lösungsvorschläge:\n"; $message .= " • Verwende Lazy Loading für eine der Abhängigkeiten\n"; $message .= " • Füge eine Factory zwischen die Klassen ein\n"; $message .= " • Refactoriere die Abhängigkeitsstruktur (Dependency Inversion)\n"; $message .= " • Verwende Event-basierte Kommunikation statt direkter Abhängigkeit\n"; return $message; } /** * Erkenne ob ein Initializer-Zyklus vorliegt * * @return array{isInitializerCycle: bool, initializerClass: string|null} */ private function detectInitializerCycle(): array { // Prüfe ob die Klasse am Zyklus-Start ein Interface oder abstrakt ist $isInterfaceOrAbstract = false; try { if (interface_exists($this->cycleStart)) { $isInterfaceOrAbstract = true; } elseif (class_exists($this->cycleStart)) { $reflection = new \ReflectionClass($this->cycleStart); $isInterfaceOrAbstract = $reflection->isAbstract(); } } catch (\Throwable) { // Ignoriere Reflection-Fehler } if (!$isInterfaceOrAbstract) { return ['isInitializerCycle' => false, 'initializerClass' => null]; } // Suche nach Initializer-Klassen in der Kette // Initializer enden typischerweise auf "Initializer" foreach ($this->fullChain as $classInChain) { if (str_ends_with($classInChain, 'Initializer')) { return [ 'isInitializerCycle' => true, 'initializerClass' => $classInChain, ]; } } return ['isInitializerCycle' => false, 'initializerClass' => null]; } /** * Get the detected cycle (without full dependency chain) */ public function getCycle(): array { return $this->cycle; } /** * Get the class where the cycle starts */ public function getCycleStart(): string { return $this->cycleStart; } }