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:
@@ -12,6 +12,8 @@ use App\Framework\DI\Exceptions\ContainerException;
|
||||
use App\Framework\DI\Exceptions\CyclicDependencyException;
|
||||
use App\Framework\DI\Exceptions\LazyLoadingException;
|
||||
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\ValueObjects\LogContext;
|
||||
use App\Framework\Metrics\FrameworkMetricsCollector;
|
||||
@@ -217,6 +219,10 @@ final class DefaultContainer implements Container
|
||||
|
||||
// Try to get DiscoveryRegistry from container and include discovered initializers
|
||||
$discoveredInitializers = [];
|
||||
$matchingInitializers = [];
|
||||
$suggestedInitializer = null;
|
||||
$failedInitializer = null;
|
||||
|
||||
if ($this->has(DiscoveryRegistry::class)) {
|
||||
try {
|
||||
$discoveryRegistry = $this->get(DiscoveryRegistry::class);
|
||||
@@ -227,17 +233,53 @@ final class DefaultContainer implements Container
|
||||
fn($attr) => $attr->className->getFullyQualified(),
|
||||
$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) {
|
||||
// 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(
|
||||
class: $class,
|
||||
dependencyChain: $this->resolving,
|
||||
availableBindings: $availableBindings,
|
||||
discoveredInitializers: $discoveredInitializers
|
||||
discoveredInitializers: $discoveredInitializers,
|
||||
matchingInitializers: $matchingInitializers,
|
||||
suggestedInitializer: $suggestedInitializer,
|
||||
failedInitializer: $failedInitializer
|
||||
);
|
||||
}
|
||||
|
||||
@@ -261,12 +303,52 @@ final class DefaultContainer implements Container
|
||||
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(
|
||||
class: $class,
|
||||
previous: $e,
|
||||
availableBindings: array_keys($this->bindings->getAllBindings()),
|
||||
dependencyChain: $this->resolving,
|
||||
bindingType: $bindingType
|
||||
bindingType: $bindingType,
|
||||
failedInitializer: $failedInitializer,
|
||||
matchingInitializer: $matchingInitializer
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\DI\Exceptions;
|
||||
|
||||
use App\Framework\DI\ValueObjects\FailedInitializer;
|
||||
use App\Framework\Exception\ExceptionContext;
|
||||
|
||||
/**
|
||||
@@ -18,12 +19,18 @@ final class ClassNotInstantiable extends ContainerException
|
||||
* @param string[] $dependencyChain
|
||||
* @param string[] $availableBindings
|
||||
* @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(
|
||||
string $class,
|
||||
array $dependencyChain,
|
||||
array $availableBindings,
|
||||
array $discoveredInitializers = []
|
||||
array $discoveredInitializers = [],
|
||||
array $matchingInitializers = [],
|
||||
?string $suggestedInitializer = null,
|
||||
?FailedInitializer $failedInitializer = null
|
||||
): self {
|
||||
// Calculate similar bindings based on class name
|
||||
$similarBindings = array_filter($availableBindings, function ($binding) use ($class) {
|
||||
@@ -35,7 +42,10 @@ final class ClassNotInstantiable extends ContainerException
|
||||
dependencyChain: $dependencyChain,
|
||||
availableBindings: $availableBindings,
|
||||
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[] $similarBindings
|
||||
* @param string[] $discoveredInitializers
|
||||
* @param string[] $matchingInitializers
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly string $class,
|
||||
@@ -52,27 +63,68 @@ final class ClassNotInstantiable extends ContainerException
|
||||
public readonly array $availableBindings,
|
||||
public readonly array $similarBindings = [],
|
||||
public readonly array $discoveredInitializers = [],
|
||||
public readonly array $matchingInitializers = [],
|
||||
public readonly ?string $suggestedInitializer = null,
|
||||
public readonly ?FailedInitializer $failedInitializer = null,
|
||||
)
|
||||
{
|
||||
$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" .
|
||||
"Dependency resolution chain: {$dependencyChainStr}\n" .
|
||||
'Total available bindings: ' . count($this->availableBindings) . "\n";
|
||||
$message = $isInterface
|
||||
? "Cannot instantiate interface '{$this->class}': interface cannot be instantiated directly.\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)) {
|
||||
$message .= 'Similar bindings found: ' . implode(', ', $this->similarBindings) . "\n";
|
||||
}
|
||||
|
||||
if (!empty($this->discoveredInitializers)) {
|
||||
|
||||
$initializers = [];
|
||||
foreach($this->discoveredInitializers as $initializer) {
|
||||
$array = explode('\\', $initializer);
|
||||
$initializers[] = end($array);
|
||||
}
|
||||
|
||||
|
||||
$message .= "Discovered initializers (" . count($this->discoveredInitializers) . "): " .
|
||||
implode(', ', $initializers) . "\n";
|
||||
} else {
|
||||
@@ -86,6 +138,9 @@ final class ClassNotInstantiable extends ContainerException
|
||||
'availableBindings' => $this->availableBindings,
|
||||
'similarBindings' => $this->similarBindings,
|
||||
'discoveredInitializers' => $this->discoveredInitializers,
|
||||
'matchingInitializers' => $this->matchingInitializers,
|
||||
'suggestedInitializer' => $this->suggestedInitializer,
|
||||
'failedInitializer' => $this->failedInitializer?->initializerClass->getFullyQualified(),
|
||||
'bindingCount' => count($this->availableBindings),
|
||||
'initializerCount' => count($this->discoveredInitializers),
|
||||
]);
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\DI\Exceptions;
|
||||
|
||||
use App\Framework\DI\ValueObjects\FailedInitializer;
|
||||
use App\Framework\Exception\ExceptionContext;
|
||||
|
||||
/**
|
||||
@@ -19,15 +20,24 @@ final class ClassResolutionException extends ContainerException
|
||||
* @param string[] $availableBindings
|
||||
* @param string[] $dependencyChain
|
||||
* @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(
|
||||
string $class,
|
||||
\Throwable $previous,
|
||||
array $availableBindings,
|
||||
array $dependencyChain,
|
||||
string $bindingType
|
||||
string $bindingType,
|
||||
?FailedInitializer $failedInitializer = null,
|
||||
?string $matchingInitializer = null
|
||||
): 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(
|
||||
class: $class,
|
||||
@@ -35,7 +45,9 @@ final class ClassResolutionException extends ContainerException
|
||||
availableBindings: $availableBindings,
|
||||
dependencyChain: $dependencyChain,
|
||||
previous: $previous,
|
||||
bindingType: $bindingType
|
||||
bindingType: $bindingType,
|
||||
failedInitializer: $failedInitializer,
|
||||
matchingInitializer: $matchingInitializer
|
||||
);
|
||||
}
|
||||
|
||||
@@ -73,11 +85,40 @@ final class ClassResolutionException extends ContainerException
|
||||
array $dependencyChain,
|
||||
int $code = 0,
|
||||
?\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}. " .
|
||||
"Available bindings: " . implode(', ', $availableBindings) .
|
||||
". Dependency chain: " . implode(' -> ', $dependencyChain);
|
||||
$dependencyChainStr = 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 = [
|
||||
'class' => $class,
|
||||
@@ -91,6 +132,17 @@ final class ClassResolutionException extends ContainerException
|
||||
$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')
|
||||
->withData($contextData);
|
||||
|
||||
|
||||
81
src/Framework/DI/FailedInitializerRegistry.php
Normal file
81
src/Framework/DI/FailedInitializerRegistry.php
Normal 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());
|
||||
}
|
||||
}
|
||||
|
||||
21
src/Framework/DI/ValueObjects/FailedInitializer.php
Normal file
21
src/Framework/DI/ValueObjects/FailedInitializer.php
Normal 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
|
||||
) {}
|
||||
}
|
||||
|
||||
@@ -10,9 +10,11 @@ use App\Framework\Core\ValueObjects\ClassName;
|
||||
use App\Framework\Core\ValueObjects\MethodName;
|
||||
use App\Framework\DI\Container;
|
||||
use App\Framework\DI\Exceptions\InitializerCycleException;
|
||||
use App\Framework\DI\FailedInitializerRegistry;
|
||||
use App\Framework\DI\Initializer;
|
||||
use App\Framework\DI\InitializerDependencyGraph;
|
||||
use App\Framework\DI\ValueObjects\DependencyGraphNode;
|
||||
use App\Framework\DI\ValueObjects\FailedInitializer;
|
||||
use App\Framework\Discovery\Results\DiscoveryRegistry;
|
||||
use App\Framework\Logging\Logger;
|
||||
use App\Framework\Logging\ValueObjects\LogContext;
|
||||
@@ -46,6 +48,8 @@ final readonly class InitializerProcessor
|
||||
$logger->debug("InitializerProcessor: Processing " . count($initializerResults) . " initializers");
|
||||
|
||||
$dependencyGraph = new InitializerDependencyGraph($this->reflectionProvider);
|
||||
/** @var FailedInitializer[] */
|
||||
$failedInitializers = [];
|
||||
|
||||
// Phase 1: Setup-Initializer sofort ausführen & Service-Initializer zum Graph hinzufügen
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
* @param FailedInitializer[] $failedInitializers
|
||||
*/
|
||||
private function registerServicesWithDependencyGraph(InitializerDependencyGraph $graph): void
|
||||
private function registerServicesWithDependencyGraph(InitializerDependencyGraph $graph, array &$failedInitializers): void
|
||||
{
|
||||
try {
|
||||
$executionOrder = $graph->getExecutionOrder();
|
||||
@@ -118,7 +139,8 @@ final readonly class InitializerProcessor
|
||||
$this->registerLazyService(
|
||||
$returnType,
|
||||
$node->getClassName(),
|
||||
$node->getMethodName()
|
||||
$node->getMethodName(),
|
||||
$failedInitializers
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -139,7 +161,8 @@ final readonly class InitializerProcessor
|
||||
$this->registerLazyService(
|
||||
$returnType,
|
||||
$node->getClassName(),
|
||||
$node->getMethodName()
|
||||
$node->getMethodName(),
|
||||
$failedInitializers
|
||||
);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
@@ -155,7 +178,8 @@ final readonly class InitializerProcessor
|
||||
$this->registerLazyService(
|
||||
$returnType,
|
||||
$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
|
||||
* @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) {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ use App\Framework\DI\DefaultContainer;
|
||||
use App\Framework\DI\Initializer;
|
||||
use App\Framework\Discovery\Results\DiscoveryRegistry;
|
||||
use App\Framework\Http\Method;
|
||||
use Exception;
|
||||
use PHPUnit\Runner\ErrorException;
|
||||
|
||||
/**
|
||||
* Verantwortlich für die Einrichtung des Routers
|
||||
|
||||
Reference in New Issue
Block a user