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\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
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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),
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
// 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();
|
$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
|
||||||
) {
|
) {
|
||||||
|
$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}. " .
|
$message = "Cannot resolve class '{$class}': {$reason}. " .
|
||||||
"Available bindings: " . implode(', ', $availableBindings) .
|
"Available bindings: " . implode(', ', $availableBindings) .
|
||||||
". Dependency chain: " . implode(' -> ', $dependencyChain);
|
". 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);
|
||||||
|
|
||||||
|
|||||||
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\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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user