feat(di): improve initializer error handling with FailedInitializerRegistry

- Add FailedInitializerRegistry to track failed initializers
- Add FailedInitializer value object to store failure context
- Enhance exception messages with failed initializer context
- Improve ClassNotInstantiable and ClassResolutionException with detailed context
- Update InitializerProcessor to register failed initializers
This commit is contained in:
2025-11-03 16:48:13 +01:00
parent c4a4f6de07
commit 8c264f3781
7 changed files with 348 additions and 22 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\DI\FailedInitializerRegistry;
use App\Framework\DI\ValueObjects\FailedInitializer;
use App\Framework\Logging\Logger; use App\Framework\Logging\Logger;
use App\Framework\Logging\ValueObjects\LogContext; use App\Framework\Logging\ValueObjects\LogContext;
use App\Framework\Metrics\FrameworkMetricsCollector; use App\Framework\Metrics\FrameworkMetricsCollector;
@@ -217,6 +219,10 @@ final class DefaultContainer implements Container
// Try to get DiscoveryRegistry from container and include discovered initializers // Try to get DiscoveryRegistry from container and include discovered initializers
$discoveredInitializers = []; $discoveredInitializers = [];
$matchingInitializers = [];
$suggestedInitializer = null;
$failedInitializer = null;
if ($this->has(DiscoveryRegistry::class)) { if ($this->has(DiscoveryRegistry::class)) {
try { try {
$discoveryRegistry = $this->get(DiscoveryRegistry::class); $discoveryRegistry = $this->get(DiscoveryRegistry::class);
@@ -227,17 +233,53 @@ final class DefaultContainer implements Container
fn($attr) => $attr->className->getFullyQualified(), fn($attr) => $attr->className->getFullyQualified(),
$initializerResults $initializerResults
); );
// Spezielle Behandlung für Interfaces: Suche nach Initializern die dieses Interface zurückgeben
if (interface_exists($class)) {
foreach ($initializerResults as $initializer) {
$returnType = $initializer->additionalData['return'] ?? null;
if ($returnType === $class) {
$matchingInitializers[] = $initializer->className->getFullyQualified();
}
}
// Vorschlag basierend auf Interface-Name (z.B. ComponentRegistryInterface -> ComponentRegistryInitializer)
$interfaceName = basename(str_replace('\\', '/', $class));
$suggestedName = str_replace('Interface', '', $interfaceName) . 'Initializer';
foreach ($discoveredInitializers as $initializer) {
if (str_contains($initializer, $suggestedName)) {
$suggestedInitializer = $initializer;
break;
}
}
}
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
// Silently ignore errors when trying to access DiscoveryRegistry to avoid masking the original error // Silently ignore errors when trying to access DiscoveryRegistry to avoid masking the original error
} }
} }
// Prüfe ob ein Initializer für dieses Interface fehlgeschlagen ist
if ($this->has(FailedInitializerRegistry::class)) {
try {
$failedRegistry = $this->get(FailedInitializerRegistry::class);
if ($failedRegistry->hasFailedInitializer($class)) {
$failedInitializer = $failedRegistry->getFailedInitializer($class);
}
} catch (\Throwable $e) {
// Silently ignore errors when trying to access FailedInitializerRegistry
}
}
throw ClassNotInstantiable::fromContainerContext( throw ClassNotInstantiable::fromContainerContext(
class: $class, class: $class,
dependencyChain: $this->resolving, dependencyChain: $this->resolving,
availableBindings: $availableBindings, availableBindings: $availableBindings,
discoveredInitializers: $discoveredInitializers discoveredInitializers: $discoveredInitializers,
matchingInitializers: $matchingInitializers,
suggestedInitializer: $suggestedInitializer,
failedInitializer: $failedInitializer
); );
} }
@@ -261,12 +303,52 @@ final class DefaultContainer implements Container
default => 'object' default => 'object'
}; };
// Für callable bindings: Prüfe ob ein Initializer fehlgeschlagen ist
$failedInitializer = null;
$matchingInitializer = null;
if ($bindingType === 'callable') {
// Prüfe ob ein fehlgeschlagener Initializer für diese Klasse existiert
if ($this->has(FailedInitializerRegistry::class)) {
try {
$failedRegistry = $this->get(FailedInitializerRegistry::class);
if ($failedRegistry->hasFailedInitializer($class)) {
$failedInitializer = $failedRegistry->getFailedInitializer($class);
}
} catch (\Throwable $registryError) {
// Silently ignore errors when trying to access FailedInitializerRegistry
}
}
// Suche auch in DiscoveryRegistry nach Initializern die diese Klasse zurückgeben
if ($failedInitializer === null && $this->has(DiscoveryRegistry::class)) {
try {
$discoveryRegistry = $this->get(DiscoveryRegistry::class);
$initializerResults = $discoveryRegistry->attributes->get(Initializer::class);
if (! empty($initializerResults)) {
foreach ($initializerResults as $initializer) {
$returnType = $initializer->additionalData['return'] ?? null;
if ($returnType === $class) {
$matchingInitializer = $initializer->className->getFullyQualified();
break;
}
}
}
} catch (\Throwable $registryError) {
// Silently ignore errors when trying to access DiscoveryRegistry
}
}
}
throw ClassResolutionException::fromBindingResolution( throw ClassResolutionException::fromBindingResolution(
class: $class, class: $class,
previous: $e, previous: $e,
availableBindings: array_keys($this->bindings->getAllBindings()), availableBindings: array_keys($this->bindings->getAllBindings()),
dependencyChain: $this->resolving, dependencyChain: $this->resolving,
bindingType: $bindingType bindingType: $bindingType,
failedInitializer: $failedInitializer,
matchingInitializer: $matchingInitializer
); );
} }
} }

View File

@@ -3,6 +3,7 @@ declare(strict_types=1);
namespace App\Framework\DI\Exceptions; namespace App\Framework\DI\Exceptions;
use App\Framework\DI\ValueObjects\FailedInitializer;
use App\Framework\Exception\ExceptionContext; use App\Framework\Exception\ExceptionContext;
/** /**
@@ -18,12 +19,18 @@ final class ClassNotInstantiable extends ContainerException
* @param string[] $dependencyChain * @param string[] $dependencyChain
* @param string[] $availableBindings * @param string[] $availableBindings
* @param string[] $discoveredInitializers * @param string[] $discoveredInitializers
* @param string[] $matchingInitializers Initializer-Klassen die dieses Interface zurückgeben
* @param string|null $suggestedInitializer Vorgeschlagener Initializer basierend auf Interface-Name
* @param FailedInitializer|null $failedInitializer Fehlgeschlagener Initializer für dieses Interface
*/ */
public static function fromContainerContext( public static function fromContainerContext(
string $class, string $class,
array $dependencyChain, array $dependencyChain,
array $availableBindings, array $availableBindings,
array $discoveredInitializers = [] array $discoveredInitializers = [],
array $matchingInitializers = [],
?string $suggestedInitializer = null,
?FailedInitializer $failedInitializer = null
): self { ): self {
// Calculate similar bindings based on class name // Calculate similar bindings based on class name
$similarBindings = array_filter($availableBindings, function ($binding) use ($class) { $similarBindings = array_filter($availableBindings, function ($binding) use ($class) {
@@ -35,7 +42,10 @@ final class ClassNotInstantiable extends ContainerException
dependencyChain: $dependencyChain, dependencyChain: $dependencyChain,
availableBindings: $availableBindings, availableBindings: $availableBindings,
similarBindings: $similarBindings, similarBindings: $similarBindings,
discoveredInitializers: $discoveredInitializers discoveredInitializers: $discoveredInitializers,
matchingInitializers: $matchingInitializers,
suggestedInitializer: $suggestedInitializer,
failedInitializer: $failedInitializer
); );
} }
@@ -45,6 +55,7 @@ final class ClassNotInstantiable extends ContainerException
* @param string[] $availableBindings * @param string[] $availableBindings
* @param string[] $similarBindings * @param string[] $similarBindings
* @param string[] $discoveredInitializers * @param string[] $discoveredInitializers
* @param string[] $matchingInitializers
*/ */
public function __construct( public function __construct(
public readonly string $class, public readonly string $class,
@@ -52,27 +63,68 @@ final class ClassNotInstantiable extends ContainerException
public readonly array $availableBindings, public readonly array $availableBindings,
public readonly array $similarBindings = [], public readonly array $similarBindings = [],
public readonly array $discoveredInitializers = [], public readonly array $discoveredInitializers = [],
public readonly array $matchingInitializers = [],
public readonly ?string $suggestedInitializer = null,
public readonly ?FailedInitializer $failedInitializer = null,
) )
{ {
$dependencyChainStr = implode(' -> ', $this->dependencyChain); $dependencyChainStr = implode(' -> ', $this->dependencyChain);
$isInterface = interface_exists($this->class);
$message = "Cannot instantiate class '{$this->class}': class is not instantiable (interface, abstract class, or trait).\n" . $message = $isInterface
"Dependency resolution chain: {$dependencyChainStr}\n" . ? "Cannot instantiate interface '{$this->class}': interface cannot be instantiated directly.\n"
'Total available bindings: ' . count($this->availableBindings) . "\n"; : "Cannot instantiate class '{$this->class}': class is not instantiable (interface, abstract class, or trait).\n";
$message .= "Dependency resolution chain: {$dependencyChainStr}\n";
// Spezielle Behandlung für Interfaces
if ($isInterface) {
// Matching Initializer
if (! empty($this->matchingInitializers)) {
$message .= "\nFound " . count($this->matchingInitializers) . " initializer(s) that return this interface:\n";
foreach ($this->matchingInitializers as $matching) {
$message .= " - {$matching}\n";
}
}
// Fehlgeschlagener Initializer
if ($this->failedInitializer !== null) {
$message .= "\n⚠️ Initializer found but failed during execution:\n";
$message .= " - Initializer: {$this->failedInitializer->initializerClass->getFullyQualified()}\n";
$message .= " - Return Type: {$this->failedInitializer->returnType->getFullyQualified()}\n";
$message .= " - Error: {$this->failedInitializer->errorMessage}\n";
$message .= " - Exception: {$this->failedInitializer->exceptionClass->getFullyQualified()}\n";
$message .= " - Check logs for full stack trace\n";
} else {
// Vorgeschlagener Initializer
if ($this->suggestedInitializer !== null) {
$message .= "\nSuggested initializer (based on interface name):\n";
$message .= " - {$this->suggestedInitializer}\n";
}
// Hinweise wenn kein Initializer gefunden wurde
if (empty($this->matchingInitializers) && $this->suggestedInitializer === null) {
$message .= "\nIf initializer exists but wasn't found in discovery:\n";
$message .= " - Try clearing discovery cache: php artisan discovery:clear\n";
$message .= " - Check if it has a ContextType filter that excludes the current context\n";
$message .= " - Ensure the initializer is registered with #[Initializer] attribute\n";
}
}
}
$message .= "\nTotal available bindings: " . count($this->availableBindings) . "\n";
if (!empty($this->similarBindings)) { if (!empty($this->similarBindings)) {
$message .= 'Similar bindings found: ' . implode(', ', $this->similarBindings) . "\n"; $message .= 'Similar bindings found: ' . implode(', ', $this->similarBindings) . "\n";
} }
if (!empty($this->discoveredInitializers)) { if (!empty($this->discoveredInitializers)) {
$initializers = []; $initializers = [];
foreach($this->discoveredInitializers as $initializer) { foreach($this->discoveredInitializers as $initializer) {
$array = explode('\\', $initializer); $array = explode('\\', $initializer);
$initializers[] = end($array); $initializers[] = end($array);
} }
$message .= "Discovered initializers (" . count($this->discoveredInitializers) . "): " . $message .= "Discovered initializers (" . count($this->discoveredInitializers) . "): " .
implode(', ', $initializers) . "\n"; implode(', ', $initializers) . "\n";
} else { } else {
@@ -86,6 +138,9 @@ final class ClassNotInstantiable extends ContainerException
'availableBindings' => $this->availableBindings, 'availableBindings' => $this->availableBindings,
'similarBindings' => $this->similarBindings, 'similarBindings' => $this->similarBindings,
'discoveredInitializers' => $this->discoveredInitializers, 'discoveredInitializers' => $this->discoveredInitializers,
'matchingInitializers' => $this->matchingInitializers,
'suggestedInitializer' => $this->suggestedInitializer,
'failedInitializer' => $this->failedInitializer?->initializerClass->getFullyQualified(),
'bindingCount' => count($this->availableBindings), 'bindingCount' => count($this->availableBindings),
'initializerCount' => count($this->discoveredInitializers), 'initializerCount' => count($this->discoveredInitializers),
]); ]);

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Framework\DI\Exceptions; namespace App\Framework\DI\Exceptions;
use App\Framework\DI\ValueObjects\FailedInitializer;
use App\Framework\Exception\ExceptionContext; use App\Framework\Exception\ExceptionContext;
/** /**
@@ -19,15 +20,24 @@ final class ClassResolutionException extends ContainerException
* @param string[] $availableBindings * @param string[] $availableBindings
* @param string[] $dependencyChain * @param string[] $dependencyChain
* @param string $bindingType One of 'callable', 'string', or 'object' * @param string $bindingType One of 'callable', 'string', or 'object'
* @param FailedInitializer|null $failedInitializer Fehlgeschlagener Initializer für diese Klasse
* @param string|null $matchingInitializer Initializer-Klasse die diese Klasse zurückgibt (falls bekannt)
*/ */
public static function fromBindingResolution( public static function fromBindingResolution(
string $class, string $class,
\Throwable $previous, \Throwable $previous,
array $availableBindings, array $availableBindings,
array $dependencyChain, array $dependencyChain,
string $bindingType string $bindingType,
?FailedInitializer $failedInitializer = null,
?string $matchingInitializer = null
): self { ): self {
$reason = "Binding resolution failed (binding type: {$bindingType}): " . $previous->getMessage(); // Für callable bindings: Spezielle Fehlermeldung wenn Initializer beteiligt ist
if ($bindingType === 'callable' && ($failedInitializer !== null || $matchingInitializer !== null)) {
$reason = "Initializer failed during execution.";
} else {
$reason = "Binding resolution failed (binding type: {$bindingType}): " . $previous->getMessage();
}
return new self( return new self(
class: $class, class: $class,
@@ -35,7 +45,9 @@ final class ClassResolutionException extends ContainerException
availableBindings: $availableBindings, availableBindings: $availableBindings,
dependencyChain: $dependencyChain, dependencyChain: $dependencyChain,
previous: $previous, previous: $previous,
bindingType: $bindingType bindingType: $bindingType,
failedInitializer: $failedInitializer,
matchingInitializer: $matchingInitializer
); );
} }
@@ -73,11 +85,40 @@ final class ClassResolutionException extends ContainerException
array $dependencyChain, array $dependencyChain,
int $code = 0, int $code = 0,
?\Throwable $previous = null, ?\Throwable $previous = null,
?string $bindingType = null ?string $bindingType = null,
public readonly ?FailedInitializer $failedInitializer = null,
public readonly ?string $matchingInitializer = null
) { ) {
$message = "Cannot resolve class '{$class}': {$reason}. " . $dependencyChainStr = implode(' -> ', $dependencyChain);
"Available bindings: " . implode(', ', $availableBindings) .
". Dependency chain: " . implode(' -> ', $dependencyChain); // Für callable bindings mit fehlgeschlagenem Initializer: Detaillierte Fehlermeldung
if ($bindingType === 'callable' && ($this->failedInitializer !== null || $this->matchingInitializer !== null)) {
$message = "Cannot resolve class '{$class}': {$reason}\n\n";
$message .= "⚠️ Initializer failed:\n";
if ($this->failedInitializer !== null) {
// Initializer wurde beim Registrieren als fehlgeschlagen getrackt
$message .= " - Initializer: {$this->failedInitializer->initializerClass->getFullyQualified()}\n";
$message .= " - Return Type: {$this->failedInitializer->returnType->getFullyQualified()}\n";
$message .= " - Error: {$this->failedInitializer->errorMessage}\n";
$message .= " - Exception: {$this->failedInitializer->exceptionClass->getFullyQualified()}\n";
} elseif ($this->matchingInitializer !== null) {
// Initializer wurde gefunden, aber nicht getrackt (Fehler beim Factory-Aufruf)
$message .= " - Initializer: {$this->matchingInitializer}\n";
$message .= " - Return Type: {$class}\n";
$errorMessage = $previous !== null ? $previous->getMessage() : $reason;
$message .= " - Error: {$errorMessage}\n";
$message .= " - Exception: " . ($previous !== null ? get_class($previous) : 'Unknown') . "\n";
}
$message .= " - Check logs for full stack trace\n\n";
$message .= "Dependency resolution chain: {$dependencyChainStr}\n";
$message .= "Available bindings: " . implode(', ', $availableBindings);
} else {
$message = "Cannot resolve class '{$class}': {$reason}. " .
"Available bindings: " . implode(', ', $availableBindings) .
". Dependency chain: {$dependencyChainStr}";
}
$contextData = [ $contextData = [
'class' => $class, 'class' => $class,
@@ -91,6 +132,17 @@ final class ClassResolutionException extends ContainerException
$contextData['bindingType'] = $bindingType; $contextData['bindingType'] = $bindingType;
} }
if ($this->failedInitializer !== null) {
$contextData['failedInitializer'] = $this->failedInitializer->initializerClass->getFullyQualified();
$contextData['failedReturnType'] = $this->failedInitializer->returnType->getFullyQualified();
$contextData['failedErrorMessage'] = $this->failedInitializer->errorMessage;
$contextData['failedExceptionClass'] = $this->failedInitializer->exceptionClass->getFullyQualified();
}
if ($this->matchingInitializer !== null) {
$contextData['matchingInitializer'] = $this->matchingInitializer;
}
$context = ExceptionContext::forOperation('class_resolution', 'DI Container') $context = ExceptionContext::forOperation('class_resolution', 'DI Container')
->withData($contextData); ->withData($contextData);

View File

@@ -0,0 +1,81 @@
<?php
declare(strict_types=1);
namespace App\Framework\DI;
use App\Framework\Core\ValueObjects\ClassName;
use App\Framework\DI\ValueObjects\FailedInitializer;
/**
* Registry für fehlgeschlagene Initializer
*
* Speichert alle Initializer die beim Ausführen fehlgeschlagen sind,
* um bessere Fehlermeldungen bei Interface-Auflösungsfehlern zu ermöglichen.
*/
final readonly class FailedInitializerRegistry
{
/** @var array<string, FailedInitializer> Indexed by return type */
private array $byReturnType;
/** @var array<string, FailedInitializer> Indexed by initializer class */
private array $byInitializerClass;
/**
* @param FailedInitializer ...$failedInitializers
*/
public function __construct(FailedInitializer ...$failedInitializers)
{
foreach ($failedInitializers as $failedInitializer) {
$returnType = $failedInitializer->returnType->getFullyQualified();
$initializerClass = $failedInitializer->initializerClass->getFullyQualified();
$byReturnType[$returnType] = $failedInitializer;
$byInitializerClass[$initializerClass] = $failedInitializer;
}
$this->byReturnType = $byReturnType;
$this->byInitializerClass = $byInitializerClass;
}
/**
* Prüft ob ein Initializer für einen Return-Type fehlgeschlagen ist
*/
public function hasFailedInitializer(string $returnType): bool
{
return isset($this->byReturnType[$returnType]);
}
/**
* Gibt einen fehlgeschlagenen Initializer für einen Return-Type zurück
*/
public function getFailedInitializer(string $returnType): ?FailedInitializer
{
return $this->byReturnType[$returnType] ?? null;
}
/**
* Gibt alle fehlgeschlagenen Initializer zurück
* @return array<FailedInitializer>
*/
public function getAll(): array
{
return array_values($this->byReturnType);
}
/**
* Gibt einen fehlgeschlagenen Initializer nach Initializer-Klasse zurück
*/
public function getByInitializerClass(string $class): ?FailedInitializer
{
return $this->byInitializerClass[$class] ?? null;
}
/**
* Gibt einen fehlgeschlagenen Initializer nach Initializer-Klasse zurück (mit ClassName)
*/
public function getByInitializerClassName(ClassName $className): ?FailedInitializer
{
return $this->getByInitializerClass($className->getFullyQualified());
}
}

View File

@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace App\Framework\DI\ValueObjects;
use App\Framework\Core\ValueObjects\ClassName;
/**
* Value Object für fehlgeschlagene Initializer
*/
final readonly class FailedInitializer
{
public function __construct(
public ClassName $initializerClass,
public ClassName $returnType,
public string $errorMessage,
public ClassName $exceptionClass
) {}
}

View File

@@ -10,9 +10,11 @@ use App\Framework\Core\ValueObjects\ClassName;
use App\Framework\Core\ValueObjects\MethodName; use App\Framework\Core\ValueObjects\MethodName;
use App\Framework\DI\Container; use App\Framework\DI\Container;
use App\Framework\DI\Exceptions\InitializerCycleException; use App\Framework\DI\Exceptions\InitializerCycleException;
use App\Framework\DI\FailedInitializerRegistry;
use App\Framework\DI\Initializer; use App\Framework\DI\Initializer;
use App\Framework\DI\InitializerDependencyGraph; use App\Framework\DI\InitializerDependencyGraph;
use App\Framework\DI\ValueObjects\DependencyGraphNode; use App\Framework\DI\ValueObjects\DependencyGraphNode;
use App\Framework\DI\ValueObjects\FailedInitializer;
use App\Framework\Discovery\Results\DiscoveryRegistry; use App\Framework\Discovery\Results\DiscoveryRegistry;
use App\Framework\Logging\Logger; use App\Framework\Logging\Logger;
use App\Framework\Logging\ValueObjects\LogContext; use App\Framework\Logging\ValueObjects\LogContext;
@@ -46,6 +48,8 @@ final readonly class InitializerProcessor
$logger->debug("InitializerProcessor: Processing " . count($initializerResults) . " initializers"); $logger->debug("InitializerProcessor: Processing " . count($initializerResults) . " initializers");
$dependencyGraph = new InitializerDependencyGraph($this->reflectionProvider); $dependencyGraph = new InitializerDependencyGraph($this->reflectionProvider);
/** @var FailedInitializer[] */
$failedInitializers = [];
// Phase 1: Setup-Initializer sofort ausführen & Service-Initializer zum Graph hinzufügen // Phase 1: Setup-Initializer sofort ausführen & Service-Initializer zum Graph hinzufügen
foreach ($initializerResults as $discoveredAttribute) { foreach ($initializerResults as $discoveredAttribute) {
@@ -94,18 +98,35 @@ final readonly class InitializerProcessor
]) ])
); );
// Track fehlgeschlagene Initializer für spätere Diagnose
if ($returnType !== null && $returnType !== 'void') {
$failedInitializers[] = new FailedInitializer(
initializerClass: $discoveredAttribute->className,
returnType: ClassName::create($returnType),
errorMessage: $e->getMessage(),
exceptionClass: ClassName::create(get_class($e))
);
}
// Skip failed initializers to prevent breaking the entire application // Skip failed initializers to prevent breaking the entire application
} }
} }
// Phase 2: Service-Initializer in optimaler Reihenfolge registrieren // Phase 2: Service-Initializer in optimaler Reihenfolge registrieren
$this->registerServicesWithDependencyGraph($dependencyGraph); $this->registerServicesWithDependencyGraph($dependencyGraph, $failedInitializers);
// Phase 3: Registry für fehlgeschlagene Initializer im Container registrieren
if (! empty($failedInitializers)) {
$registry = new FailedInitializerRegistry(...$failedInitializers);
$this->container->instance(FailedInitializerRegistry::class, $registry);
}
} }
/** /**
* Registriert Services basierend auf Dependency-Graph in optimaler Reihenfolge * Registriert Services basierend auf Dependency-Graph in optimaler Reihenfolge
* @param FailedInitializer[] $failedInitializers
*/ */
private function registerServicesWithDependencyGraph(InitializerDependencyGraph $graph): void private function registerServicesWithDependencyGraph(InitializerDependencyGraph $graph, array &$failedInitializers): void
{ {
try { try {
$executionOrder = $graph->getExecutionOrder(); $executionOrder = $graph->getExecutionOrder();
@@ -118,7 +139,8 @@ final readonly class InitializerProcessor
$this->registerLazyService( $this->registerLazyService(
$returnType, $returnType,
$node->getClassName(), $node->getClassName(),
$node->getMethodName() $node->getMethodName(),
$failedInitializers
); );
} }
} }
@@ -139,7 +161,8 @@ final readonly class InitializerProcessor
$this->registerLazyService( $this->registerLazyService(
$returnType, $returnType,
$node->getClassName(), $node->getClassName(),
$node->getMethodName() $node->getMethodName(),
$failedInitializers
); );
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
@@ -155,7 +178,8 @@ final readonly class InitializerProcessor
$this->registerLazyService( $this->registerLazyService(
$returnType, $returnType,
$node->getClassName(), $node->getClassName(),
$node->getMethodName() $node->getMethodName(),
$failedInitializers
); );
} }
} }
@@ -186,8 +210,9 @@ final readonly class InitializerProcessor
/** /**
* Registriert einen Service lazy im Container mit Dual-Registration für Interfaces * Registriert einen Service lazy im Container mit Dual-Registration für Interfaces
* @param FailedInitializer[] $failedInitializers
*/ */
private function registerLazyService(string $returnType, string $className, string $methodName): void private function registerLazyService(string $returnType, string $className, string $methodName, array &$failedInitializers): void
{ {
$factory = function ($container) use ($className, $methodName, $returnType) { $factory = function ($container) use ($className, $methodName, $returnType) {
try { try {
@@ -222,6 +247,14 @@ final readonly class InitializerProcessor
]) ])
); );
// Track fehlgeschlagenen Initializer für spätere Diagnose
$failedInitializers[] = new FailedInitializer(
initializerClass: ClassName::create($className),
returnType: ClassName::create($returnType),
errorMessage: $e->getMessage(),
exceptionClass: ClassName::create(get_class($e))
);
// Service registration failed - continue to prevent breaking the entire application // Service registration failed - continue to prevent breaking the entire application
} }
} }

View File

@@ -15,6 +15,8 @@ use App\Framework\DI\DefaultContainer;
use App\Framework\DI\Initializer; use App\Framework\DI\Initializer;
use App\Framework\Discovery\Results\DiscoveryRegistry; use App\Framework\Discovery\Results\DiscoveryRegistry;
use App\Framework\Http\Method; use App\Framework\Http\Method;
use Exception;
use PHPUnit\Runner\ErrorException;
/** /**
* Verantwortlich für die Einrichtung des Routers * Verantwortlich für die Einrichtung des Routers