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