refactor: improve lazy loading and logging in DI container

- Enhance lazy loading fallback with detailed logging of failures
- Simplify singleton resolution logic, removing redundancy
- Add `canAutoWire` method for cleaner class auto-wiring checks
- Optimize logging resolution in `InitializerProcessor`
- Remove unused code and redundant debug logs across DI components
This commit is contained in:
2025-11-03 15:12:54 +01:00
parent b6bff8b19b
commit 376fcd5fc1
3 changed files with 71 additions and 65 deletions

View File

@@ -12,6 +12,8 @@ use App\Framework\DI\Exceptions\ContainerException;
use App\Framework\DI\Exceptions\CyclicDependencyException; use App\Framework\DI\Exceptions\CyclicDependencyException;
use App\Framework\DI\Exceptions\LazyLoadingException; use App\Framework\DI\Exceptions\LazyLoadingException;
use App\Framework\Discovery\Results\DiscoveryRegistry; use App\Framework\Discovery\Results\DiscoveryRegistry;
use App\Framework\Logging\Logger;
use App\Framework\Logging\ValueObjects\LogContext;
use App\Framework\Metrics\FrameworkMetricsCollector; use App\Framework\Metrics\FrameworkMetricsCollector;
use App\Framework\Reflection\CachedReflectionProvider; use App\Framework\Reflection\CachedReflectionProvider;
use App\Framework\Reflection\ReflectionProvider; use App\Framework\Reflection\ReflectionProvider;
@@ -93,12 +95,10 @@ final class DefaultContainer implements Container
$className = (string) $class; $className = (string) $class;
// Bereits instanziierte Objekte zurückgeben // Bereits instanziierte Objekte zurückgeben
if ($this->instances->hasSingleton($className)) {
$singleton = $this->instances->getSingleton($className); $singleton = $this->instances->getSingleton($className);
if ($singleton !== null) { if ($singleton !== null) {
return $singleton; return $singleton;
} }
}
if ($this->instances->hasInstance($className)) { if ($this->instances->hasInstance($className)) {
return $this->instances->getInstance($className); return $this->instances->getInstance($className);
@@ -108,10 +108,23 @@ final class DefaultContainer implements Container
if ($this->lazyInstantiator->canUseLazyLoading($className, $this->instances)) { if ($this->lazyInstantiator->canUseLazyLoading($className, $this->instances)) {
try { try {
return $this->lazyInstantiator->createLazyInstance($className); return $this->lazyInstantiator->createLazyInstance($className);
} catch (Throwable $e) { } catch (LazyLoadingException $e) {
if (! $e instanceof LazyLoadingException) { // Log LazyLoading-Fehler und versuche Fallback (Logger ist immer verfügbar)
$this->get(Logger::class)->warning(
"Lazy loading failed for {$className}, falling back to normal instantiation",
LogContext::withException($e)
);
// Fallback: Versuche normale Instanziierung
try {
return $this->createInstance($className);
} catch (\Throwable $fallbackException) {
// Wenn Fallback auch fehlschlägt, ursprüngliche LazyLoadingException werfen
throw $e; throw $e;
} }
} catch (\Throwable $e) {
// Andere Exceptions direkt weiterwerfen
throw $e;
} }
} }
@@ -178,7 +191,7 @@ final class DefaultContainer implements Container
// Check if class is instantiable using the framework's method // Check if class is instantiable using the framework's method
if (! $reflection->isInstantiable()) { if (! $reflection->isInstantiable()) {
$this->throwDetailedBindingException($class/*, $reflection*/); $this->throwDetailedBindingException($class);
} }
$dependencies = $this->dependencyResolver->resolveDependencies($className); $dependencies = $this->dependencyResolver->resolveDependencies($className);
@@ -198,7 +211,7 @@ final class DefaultContainer implements Container
} }
} }
private function throwDetailedBindingException(string $class/*, $reflection*/): never private function throwDetailedBindingException(string $class): never
{ {
$availableBindings = array_keys($this->bindings->getAllBindings()); $availableBindings = array_keys($this->bindings->getAllBindings());
@@ -265,7 +278,29 @@ final class DefaultContainer implements Container
return $this->instances->hasSingleton($class) return $this->instances->hasSingleton($class)
|| $this->instances->hasInstance($class) || $this->instances->hasInstance($class)
|| $this->bindings->hasBinding($class) || $this->bindings->hasBinding($class)
|| (! empty($class) && ClassName::create($class)->exists() && $this->reflectionProvider->getClass(ClassName::create($class))->isInstantiable()); || $this->canAutoWire($class);
}
/**
* Prüft ob eine Klasse automatisch instanziiert werden kann (auto-wiring)
* @param string $class Klassenname
*/
private function canAutoWire(string $class): bool
{
if (empty($class)) {
return false;
}
$className = ClassName::create($class);
if (! $className->exists()) {
return false;
}
try {
return $this->reflectionProvider->getClass($className)->isInstantiable();
} catch (\Throwable $e) {
return false;
}
} }
/** @param class-string $class */ /** @param class-string $class */

View File

@@ -38,7 +38,6 @@ final class InitializerDependencyGraph
return; // Setup-Initializer werden sofort ausgeführt return; // Setup-Initializer werden sofort ausgeführt
} }
$dependencies = $this->analyzeDependencies($className, $methodName); $dependencies = $this->analyzeDependencies($className, $methodName);
$node = new DependencyGraphNode( $node = new DependencyGraphNode(
@@ -48,7 +47,6 @@ final class InitializerDependencyGraph
dependencies: $dependencies dependencies: $dependencies
); );
$this->nodes[$returnType] = $node; $this->nodes[$returnType] = $node;
$this->adjacencyList[$returnType] = $dependencies; $this->adjacencyList[$returnType] = $dependencies;
} }
@@ -77,16 +75,7 @@ final class InitializerDependencyGraph
*/ */
public function hasNode(string $returnType): bool public function hasNode(string $returnType): bool
{ {
$exists = isset($this->nodes[$returnType]); return isset($this->nodes[$returnType]);
// Debug RouterSetup specifically
if ($returnType === 'App\\Framework\\Router\\Router') {
error_log("InitializerDependencyGraph: hasNode check for Router - exists: " . ($exists ? 'YES' : 'NO'));
error_log("InitializerDependencyGraph: Current node count: " . count($this->nodes));
error_log("InitializerDependencyGraph: Available nodes: " . implode(', ', array_keys($this->nodes)));
}
return $exists;
} }
/** /**
@@ -121,13 +110,11 @@ final class InitializerDependencyGraph
/** /**
* Analysiert die Dependencies eines Initializers durch Reflection * Analysiert die Dependencies eines Initializers durch Reflection
*/ */
private function analyzeDependencies(CLassName $className, MethodName $methodName): array private function analyzeDependencies(ClassName $className, MethodName $methodName): array
{ {
try { try {
$reflection = $this->reflectionProvider->getClass($className); $reflection = $this->reflectionProvider->getClass($className);
#$reflection = new \ReflectionClass($className);
$method = $reflection->getMethod($methodName->toString()); $method = $reflection->getMethod($methodName->toString());
;
$dependencies = []; $dependencies = [];
foreach ($method->getParameters() as $parameter) { foreach ($method->getParameters() as $parameter) {
@@ -172,26 +159,4 @@ final class InitializerDependencyGraph
$this->inStack[$returnType] = false; $this->inStack[$returnType] = false;
$result[] = $returnType; $result[] = $returnType;
} }
private function hasCycle(string $returnType): bool
{
$this->visited[$returnType] = true;
$this->inStack[$returnType] = true;
foreach ($this->adjacencyList[$returnType] ?? [] as $dependency) {
if (isset($this->nodes[$dependency])) {
if (isset($this->inStack[$dependency]) && $this->inStack[$dependency]) {
return true;
}
if (! isset($this->visited[$dependency]) && $this->hasCycle($dependency)) {
return true;
}
}
}
$this->inStack[$returnType] = false;
return false;
}
} }

View File

@@ -39,11 +39,10 @@ final readonly class InitializerProcessor
*/ */
public function processInitializers(DiscoveryRegistry $results): void public function processInitializers(DiscoveryRegistry $results): void
{ {
// Safe Logger resolution - use if available, otherwise rely on error_log $logger = $this->getLogger();
$logger = $this->container->get(Logger::class);
$initializerResults = $results->attributes->get(Initializer::class); $initializerResults = $results->attributes->get(Initializer::class);
$logger?->debug("InitializerProcessor: Processing " . count($initializerResults) . " initializers"); $logger->debug("InitializerProcessor: Processing " . count($initializerResults) . " initializers");
$dependencyGraph = new InitializerDependencyGraph($this->reflectionProvider); $dependencyGraph = new InitializerDependencyGraph($this->reflectionProvider);
@@ -63,7 +62,11 @@ final readonly class InitializerProcessor
// Context-Filter: Prüfe ob Initializer für aktuellen Context erlaubt ist // Context-Filter: Prüfe ob Initializer für aktuellen Context erlaubt ist
// The contexts data is directly in the additionalData // The contexts data is directly in the additionalData
// Wenn contexts null ist, ist der Initializer für alle Contexts verfügbar // Wenn contexts null ist, ist der Initializer für alle Contexts verfügbar
if (isset($initializerData['contexts']) && $initializerData['contexts'] !== null && ! $this->isContextAllowed(...$initializerData['contexts'])) { if (
isset($initializerData['contexts'])
&& $initializerData['contexts'] !== null
&& ! $this->isContextAllowed(...$initializerData['contexts'])
) {
continue; continue;
} }
@@ -81,7 +84,7 @@ final readonly class InitializerProcessor
$dependencyGraph->addInitializer($returnType, $discoveredAttribute->className, $methodName); $dependencyGraph->addInitializer($returnType, $discoveredAttribute->className, $methodName);
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
$logger?->error( $logger->error(
"Failed to register initializer: {$discoveredAttribute->className->toString()}::{$methodName->toString()}", "Failed to register initializer: {$discoveredAttribute->className->toString()}::{$methodName->toString()}",
LogContext::withExceptionAndData($e, [ LogContext::withExceptionAndData($e, [
'class' => $discoveredAttribute->className->toString(), 'class' => $discoveredAttribute->className->toString(),
@@ -107,13 +110,10 @@ final readonly class InitializerProcessor
$executionOrder = $graph->getExecutionOrder(); $executionOrder = $graph->getExecutionOrder();
foreach ($executionOrder as $returnType) { foreach ($executionOrder as $returnType) {
if ($graph->hasNode($returnType)) { if ($graph->hasNode($returnType)) {
/** @var DependencyGraphNode $node */ /** @var DependencyGraphNode $node */
$node = $graph->getNode($returnType); $node = $graph->getNode($returnType);
$this->registerLazyService( $this->registerLazyService(
$returnType, $returnType,
$node->getClassName(), $node->getClassName(),
@@ -123,6 +123,12 @@ final readonly class InitializerProcessor
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
// Fallback: Registriere alle Services ohne spezielle Reihenfolge // Fallback: Registriere alle Services ohne spezielle Reihenfolge
$logger = $this->getLogger();
$logger->warning(
"Failed to register services with dependency graph, falling back to unordered registration",
LogContext::withException($e)
);
/** @var string $returnType */ /** @var string $returnType */
foreach ($graph->getNodes() as $returnType => $node) { foreach ($graph->getNodes() as $returnType => $node) {
$this->registerLazyService( $this->registerLazyService(
@@ -132,8 +138,6 @@ final readonly class InitializerProcessor
); );
} }
} }
unset($graph);
} }
/** /**
@@ -164,9 +168,7 @@ final readonly class InitializerProcessor
*/ */
private function registerLazyService(string $returnType, string $className, string $methodName): void private function registerLazyService(string $returnType, string $className, string $methodName): void
{ {
$factory = function ($container) use ($className, $methodName, $returnType) { $factory = function ($container) use ($className, $methodName, $returnType) {
try { try {
$instance = $container->invoker->invoke(ClassName::create($className), $methodName); $instance = $container->invoker->invoke(ClassName::create($className), $methodName);
} catch (\Throwable $e) { } catch (\Throwable $e) {
@@ -174,7 +176,6 @@ final readonly class InitializerProcessor
throw $e; throw $e;
} }
// Wenn das ein Interface ist, registriere auch die konkrete Klasse automatisch // Wenn das ein Interface ist, registriere auch die konkrete Klasse automatisch
if (interface_exists($returnType)) { if (interface_exists($returnType)) {
$concreteClass = get_class($instance); $concreteClass = get_class($instance);
@@ -189,12 +190,9 @@ final readonly class InitializerProcessor
try { try {
// Registriere den Return-Type (Interface oder konkrete Klasse) // Registriere den Return-Type (Interface oder konkrete Klasse)
$this->container->singleton($returnType, $factory); $this->container->singleton($returnType, $factory);
} catch (\Throwable $e) { } catch (\Throwable $e) {
// Safe Logger resolution - use if available $logger = $this->getLogger();
$logger = $this->container->has(Logger::class) ? $this->container->get(Logger::class) : null; $logger->error(
$logger?->error(
"Failed to register lazy service for return type: {$returnType}", "Failed to register lazy service for return type: {$returnType}",
LogContext::withExceptionAndData($e, [ LogContext::withExceptionAndData($e, [
'return_type' => $returnType, 'return_type' => $returnType,
@@ -206,4 +204,12 @@ final readonly class InitializerProcessor
// Service registration failed - continue to prevent breaking the entire application // Service registration failed - continue to prevent breaking the entire application
} }
} }
/**
* Gibt den Logger zurück (ist im Framework immer verfügbar)
*/
private function getLogger(): Logger
{
return $this->container->get(Logger::class);
}
} }