refactor(di): improve cyclic dependency handling and error messages
- Replace `FrameworkException` with `ContainerException` in `CyclicDependencyException` - Add detection logic for cycle start and improved message formatting - Provide actionable resolution tips in error messages - Enhance `ClassResolutionException` to reuse `CyclicDependencyException` messages - Add context for available bindings when a cycle is detected
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user