diff --git a/src/Framework/DI/Exceptions/ClassResolutionException.php b/src/Framework/DI/Exceptions/ClassResolutionException.php index b65fb666..9945e151 100644 --- a/src/Framework/DI/Exceptions/ClassResolutionException.php +++ b/src/Framework/DI/Exceptions/ClassResolutionException.php @@ -32,8 +32,12 @@ final class ClassResolutionException extends ContainerException ?FailedInitializer $failedInitializer = null, ?string $matchingInitializer = null ): self { - // Für callable bindings: Spezielle Fehlermeldung wenn Initializer beteiligt ist - if ($bindingType === 'callable' && ($failedInitializer !== null || $matchingInitializer !== null)) { + // Spezialbehandlung für zyklische Abhängigkeiten + if ($previous instanceof CyclicDependencyException) { + // Nutze die verbesserte Meldung der CyclicDependencyException direkt + $reason = $previous->getMessage(); + } elseif ($bindingType === 'callable' && ($failedInitializer !== null || $matchingInitializer !== null)) { + // Für callable bindings: Spezielle Fehlermeldung wenn Initializer beteiligt ist $reason = "Initializer failed during execution."; } else { $reason = "Binding resolution failed (binding type: {$bindingType}): " . $previous->getMessage(); @@ -65,9 +69,16 @@ final class ClassResolutionException extends ContainerException array $availableBindings, array $dependencyChain ): self { + // Spezialbehandlung für zyklische Abhängigkeiten + // CyclicDependencyException extends FrameworkException extends RuntimeException, + // daher wird es hier auch erkannt + $reason = $exception instanceof CyclicDependencyException + ? $exception->getMessage() + : $exception->getMessage(); + return new self( class: $class, - reason: $exception->getMessage(), + reason: $reason, availableBindings: $availableBindings, dependencyChain: $dependencyChain, previous: $exception @@ -91,8 +102,22 @@ final class ClassResolutionException extends ContainerException ) { $dependencyChainStr = implode(' -> ', $dependencyChain); - // Für callable bindings mit fehlgeschlagenem Initializer: Detaillierte Fehlermeldung - if ($bindingType === 'callable' && ($this->failedInitializer !== null || $this->matchingInitializer !== null)) { + // Spezialbehandlung für zyklische Abhängigkeiten: Nutze die verbesserte Meldung direkt + if ($previous instanceof CyclicDependencyException) { + // Die CyclicDependencyException hat bereits eine übersichtliche Formatierung + // Füge nur zusätzlichen Kontext hinzu, wenn relevant + $message = $reason; // Nutze die bereits formatierte Meldung aus CyclicDependencyException + + // Füge zusätzliche Informationen nur hinzu, wenn sie hilfreich sind + if (!empty($availableBindings)) { + $message .= "\n\n"; + $message .= "📋 Verfügbare Bindings: " . implode(', ', array_slice($availableBindings, 0, 10)); + if (count($availableBindings) > 10) { + $message .= " (und " . (count($availableBindings) - 10) . " weitere)"; + } + } + } elseif ($bindingType === 'callable' && ($this->failedInitializer !== null || $this->matchingInitializer !== null)) { + // Für callable bindings mit fehlgeschlagenem Initializer: Detaillierte Fehlermeldung $message = "Cannot resolve class '{$class}': {$reason}\n\n"; $message .= "⚠️ Initializer failed:\n"; diff --git a/src/Framework/DI/Exceptions/CyclicDependencyException.php b/src/Framework/DI/Exceptions/CyclicDependencyException.php index c97921d0..d7a1e347 100644 --- a/src/Framework/DI/Exceptions/CyclicDependencyException.php +++ b/src/Framework/DI/Exceptions/CyclicDependencyException.php @@ -5,27 +5,83 @@ declare(strict_types=1); namespace App\Framework\DI\Exceptions; use App\Framework\Exception\ExceptionContext; -use App\Framework\Exception\FrameworkException; -final class CyclicDependencyException extends FrameworkException +final class CyclicDependencyException extends ContainerException { + private readonly array $cycle; + private readonly string $cycleStart; + public function __construct( array $dependencyChain, string $class, int $code = 0, ?\Throwable $previous = null ) { + // Finde wo der Zyklus beginnt (erste Wiederholung in der Kette) + $cycleStartIndex = array_search($class, $dependencyChain, true); + + if ($cycleStartIndex !== false) { + // Extrahiere nur den Zyklus-Teil + $this->cycle = array_slice($dependencyChain, $cycleStartIndex); + $this->cycle[] = $class; // Schließe den Zyklus + $this->cycleStart = $dependencyChain[$cycleStartIndex]; + } else { + // Fallback: Verwende die gesamte Kette + $this->cycle = array_merge($dependencyChain, [$class]); + $this->cycleStart = $dependencyChain[0] ?? $class; + } + $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: 'Zyklische Abhängigkeit entdeckt: ' . implode(' -> ', $dependencyChain) . " -> $class", + message: $message, context: $context, code: $code, previous: $previous ); } + + private function buildMessage(): string + { + $cycleStr = implode(' → ', $this->cycle); + + $message = "🔄 Zyklische Abhängigkeit entdeckt:\n\n"; + $message .= " {$cycleStr}\n"; + $message .= " ↑─────────────────────┘\n"; + $message .= " Der Zyklus beginnt hier bei '{$this->cycleStart}'\n\n"; + + // Füge hilfreiche Hinweise hinzu + $message .= "💡 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; + } + + /** + * 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; + } }