refactor(di): add debug logging for dependency and cycle analysis

- Enhance `CyclicDependencyException` and `InitializerDependencyAnalyzer` with detailed debug logging for improved diagnostics.
- Add logs for cycle detection, dependency path analysis, and interface implementation resolution.
- Refine try-catch blocks and exception handling for more granular error tracing.
This commit is contained in:
2025-11-03 21:30:12 +01:00
parent f1888b0448
commit f4f367bae4
2 changed files with 81 additions and 8 deletions

View File

@@ -7,6 +7,7 @@ namespace App\Framework\DI\Exceptions;
use App\Framework\DI\Container; use App\Framework\DI\Container;
use App\Framework\DI\InitializerDependencyAnalyzer; use App\Framework\DI\InitializerDependencyAnalyzer;
use App\Framework\Exception\ExceptionContext; use App\Framework\Exception\ExceptionContext;
use Throwable;
final class CyclicDependencyException extends ContainerException final class CyclicDependencyException extends ContainerException
{ {
@@ -17,10 +18,10 @@ final class CyclicDependencyException extends ContainerException
private readonly ?InitializerDependencyAnalyzer $dependencyAnalyzer; private readonly ?InitializerDependencyAnalyzer $dependencyAnalyzer;
public function __construct( public function __construct(
array $dependencyChain, array $dependencyChain,
string $class, string $class,
int $code = 0, int $code = 0,
?\Throwable $previous = null, ?Throwable $previous = null,
?InitializerDependencyAnalyzer $dependencyAnalyzer = null ?InitializerDependencyAnalyzer $dependencyAnalyzer = null
) { ) {
// Speichere vollständige Kette // Speichere vollständige Kette
@@ -261,21 +262,40 @@ final class CyclicDependencyException extends ContainerException
// Verwende Analyzer aus Exception (wenn verfügbar) // Verwende Analyzer aus Exception (wenn verfügbar)
$analyzer = $this->dependencyAnalyzer ?? new InitializerDependencyAnalyzer(); $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 // Methode 1: Rekursive Suche - Finde vollständigen Pfad für jede Dependency
foreach ($initializerDependencies as $dependency) { foreach ($initializerDependencies as $dependency) {
// Überspringe Container selbst // Überspringe Container selbst
if ($dependency === Container::class || $dependency === 'App\Framework\DI\Container') { if ($dependency === Container::class || $dependency === 'App\Framework\DI\Container') {
error_log(sprintf("[CYCLIC_DEBUG] Überspringe Container '%s'", $dependency));
continue; continue;
} }
error_log(sprintf("[CYCLIC_DEBUG] Prüfe Dependency '%s' auf Pfad zu '%s'", $dependency, $interface));
$path = $analyzer->findDependencyPathToInterface($dependency, $interface); $path = $analyzer->findDependencyPathToInterface($dependency, $interface);
if ($path !== null && !empty($path)) { 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 // Der erste Eintrag im Pfad ist die Dependency, die das Interface benötigt
return [ return [
'problematicDependency' => $dependency, 'problematicDependency' => $dependency,
'confirmationMethod' => 'recursive_analysis', 'confirmationMethod' => 'recursive_analysis',
'fullPath' => $path, '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; return false;
} catch (\Throwable) { } catch (Throwable) {
return false; return false;
} }
} }
@@ -405,7 +425,7 @@ final class CyclicDependencyException extends ContainerException
$reflection = new \ReflectionClass($this->cycleStart); $reflection = new \ReflectionClass($this->cycleStart);
$isInterfaceOrAbstract = $reflection->isAbstract(); $isInterfaceOrAbstract = $reflection->isAbstract();
} }
} catch (\Throwable) { } catch (Throwable) {
// Ignoriere Reflection-Fehler // Ignoriere Reflection-Fehler
} }

View File

@@ -247,25 +247,46 @@ final readonly class InitializerDependencyAnalyzer
array $currentPath = [], array $currentPath = [],
int $depth = 0 int $depth = 0
): ?array { ): ?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 // Max. Rekursionstiefe erreicht
if ($depth >= self::MAX_RECURSION_DEPTH) { if ($depth >= self::MAX_RECURSION_DEPTH) {
error_log(sprintf("[DEPENDENCY_DEBUG] Max. Tiefe erreicht für '%s'", $dependencyClass));
return null; return null;
} }
// Cycle-Detection: Vermeide Endlosschleifen // Cycle-Detection: Vermeide Endlosschleifen
if (in_array($dependencyClass, $visited, true)) { if (in_array($dependencyClass, $visited, true)) {
error_log(sprintf("[DEPENDENCY_DEBUG] Cycle erkannt für '%s' (bereits besucht)", $dependencyClass));
return null; return null;
} }
// Prüfe ob diese Klasse direkt das Interface benötigt // Prüfe ob diese Klasse direkt das Interface benötigt
if ($this->dependencyNeedsInterface($dependencyClass, $targetInterface)) { 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 // Rekursiv: Analysiere Dependencies dieser Klasse
$dependencies = $this->getClassDependencies($dependencyClass); $dependencies = $this->getClassDependencies($dependencyClass);
error_log(sprintf(
"[DEPENDENCY_DEBUG] '%s' hat %d Dependencies: [%s]",
$dependencyClass,
count($dependencies),
implode(', ', $dependencies)
));
if (empty($dependencies)) { if (empty($dependencies)) {
error_log(sprintf("[DEPENDENCY_DEBUG] Keine Dependencies für '%s'", $dependencyClass));
return null; return null;
} }
@@ -275,9 +296,16 @@ final readonly class InitializerDependencyAnalyzer
foreach ($dependencies as $subDependency) { foreach ($dependencies as $subDependency) {
// Überspringe Container selbst (würde alle Dependencies auflisten) // Überspringe Container selbst (würde alle Dependencies auflisten)
if ($subDependency === Container::class || $subDependency === 'App\Framework\DI\Container') { if ($subDependency === Container::class || $subDependency === 'App\Framework\DI\Container') {
error_log(sprintf("[DEPENDENCY_DEBUG] Überspringe Container '%s'", $subDependency));
continue; continue;
} }
error_log(sprintf(
"[DEPENDENCY_DEBUG] Prüfe Sub-Dependency '%s' (Tiefe %d)",
$subDependency,
$depth + 1
));
$path = $this->findDependencyPathToInterface( $path = $this->findDependencyPathToInterface(
$subDependency, $subDependency,
$targetInterface, $targetInterface,
@@ -287,10 +315,12 @@ final readonly class InitializerDependencyAnalyzer
); );
if ($path !== null) { if ($path !== null) {
error_log(sprintf("[DEPENDENCY_DEBUG] ✅ Pfad über '%s' gefunden!", $subDependency));
return $path; return $path;
} }
} }
error_log(sprintf("[DEPENDENCY_DEBUG] ❌ Kein Pfad gefunden für '%s'", $dependencyClass));
return null; return null;
} }
@@ -364,6 +394,7 @@ final readonly class InitializerDependencyAnalyzer
{ {
try { try {
if (!class_exists($className) && !interface_exists($className)) { if (!class_exists($className) && !interface_exists($className)) {
error_log(sprintf("[DEPENDENCY_DEBUG] Klasse/Interface existiert nicht: '%s'", $className));
return []; return [];
} }
@@ -371,11 +402,14 @@ final readonly class InitializerDependencyAnalyzer
// Wenn es ein Interface ist, versuche die Implementierung zu finden // Wenn es ein Interface ist, versuche die Implementierung zu finden
if ($reflection->isInterface()) { if ($reflection->isInterface()) {
error_log(sprintf("[DEPENDENCY_DEBUG] '%s' ist Interface, suche Implementierung...", $className));
// Suche nach bekannten Implementierungen (basierend auf Namenskonvention) // Suche nach bekannten Implementierungen (basierend auf Namenskonvention)
$implClass = $this->findInterfaceImplementation($className); $implClass = $this->findInterfaceImplementation($className);
if ($implClass !== null && class_exists($implClass)) { if ($implClass !== null && class_exists($implClass)) {
error_log(sprintf("[DEPENDENCY_DEBUG] ✅ Implementierung gefunden: '%s' → '%s'", $className, $implClass));
$reflection = new \ReflectionClass($implClass); $reflection = new \ReflectionClass($implClass);
} else { } else {
error_log(sprintf("[DEPENDENCY_DEBUG] ❌ Keine Implementierung für Interface '%s' gefunden", $className));
// Kann keine Dependencies für Interfaces ohne Implementierung finden // Kann keine Dependencies für Interfaces ohne Implementierung finden
return []; return [];
} }
@@ -530,31 +564,50 @@ final readonly class InitializerDependencyAnalyzer
*/ */
private function findInterfaceImplementation(string $interface): ?string 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) // 1. Versuche über Container-Bindings (falls Container verfügbar)
if ($this->container !== null) { if ($this->container !== null) {
try { try {
error_log(sprintf("[DEPENDENCY_DEBUG] Container verfügbar, prüfe has('%s')", $interface));
// Prüfe ob Interface gebunden ist // Prüfe ob Interface gebunden ist
if ($this->container->has($interface)) { if ($this->container->has($interface)) {
error_log(sprintf("[DEPENDENCY_DEBUG] Interface '%s' hat Binding, hole Binding...", $interface));
$binding = $this->getBindingForInterface($interface); $binding = $this->getBindingForInterface($interface);
if ($binding !== null) { 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 // Wenn Binding ein String ist (Klassenname), verwende diesen
if (is_string($binding) && class_exists($binding)) { if (is_string($binding) && class_exists($binding)) {
error_log(sprintf("[DEPENDENCY_DEBUG] ✅ Verwende String-Binding: '%s'", $binding));
return $binding; return $binding;
} }
// Wenn Binding ein Objekt ist, verwende dessen Klasse // Wenn Binding ein Objekt ist, verwende dessen Klasse
if (is_object($binding)) { if (is_object($binding)) {
error_log(sprintf("[DEPENDENCY_DEBUG] ✅ Verwende Objekt-Binding: '%s'", $binding::class));
return $binding::class; return $binding::class;
} }
// Wenn Binding ein Callable ist, können wir es nicht direkt auflösen // Wenn Binding ein Callable ist, können wir es nicht direkt auflösen
// aber wir können versuchen, es zu instanziieren (wenn kein Zyklus) // aber wir können versuchen, es zu instanziieren (wenn kein Zyklus)
// Das ist riskant, also überspringen wir es für jetzt // 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 // Container-Zugriff fehlgeschlagen - ignoriere und versuche Fallback
} }
} else {
error_log(sprintf("[DEPENDENCY_DEBUG] ❌ Container nicht verfügbar"));
} }
// 2. Versuche Namenskonvention: Interface -> DefaultInterfaceName // 2. Versuche Namenskonvention: Interface -> DefaultInterfaceName