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:
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user