- 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
216 lines
8.2 KiB
PHP
216 lines
8.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\Discovery;
|
|
|
|
use App\Framework\Context\ContextType;
|
|
use App\Framework\Context\ExecutionContext;
|
|
use App\Framework\Core\ValueObjects\ClassName;
|
|
use App\Framework\Core\ValueObjects\MethodName;
|
|
use App\Framework\DI\Container;
|
|
use App\Framework\DI\Initializer;
|
|
use App\Framework\DI\InitializerDependencyGraph;
|
|
use App\Framework\DI\ValueObjects\DependencyGraphNode;
|
|
use App\Framework\Discovery\Results\DiscoveryRegistry;
|
|
use App\Framework\Logging\Logger;
|
|
use App\Framework\Logging\ValueObjects\LogContext;
|
|
use App\Framework\Reflection\ReflectionProvider;
|
|
|
|
/**
|
|
* Processes and executes Initializers discovered by the Discovery system
|
|
*
|
|
* Handles context filtering, dependency graph construction, and lazy service registration.
|
|
* Separated from DiscoveryServiceBootstrapper for Single Responsibility Principle.
|
|
*/
|
|
final readonly class InitializerProcessor
|
|
{
|
|
public function __construct(
|
|
private Container $container,
|
|
private ReflectionProvider $reflectionProvider,
|
|
private ExecutionContext $executionContext,
|
|
) {}
|
|
|
|
/**
|
|
* Intelligente Initializer-Verarbeitung mit Dependency-Graph:
|
|
* - Context-Filter: Nur für passende Execution-Contexts
|
|
* - void/null Return: Sofort ausführen (Setup)
|
|
* - Konkreter Return-Type: Dependency-Graph basierte Registrierung
|
|
*/
|
|
public function processInitializers(DiscoveryRegistry $results): void
|
|
{
|
|
$logger = $this->getLogger();
|
|
$initializerResults = $results->attributes->get(Initializer::class);
|
|
|
|
$logger->debug("InitializerProcessor: Processing " . count($initializerResults) . " initializers");
|
|
|
|
$dependencyGraph = new InitializerDependencyGraph($this->reflectionProvider);
|
|
|
|
// Phase 1: Setup-Initializer sofort ausführen & Service-Initializer zum Graph hinzufügen
|
|
foreach ($initializerResults as $discoveredAttribute) {
|
|
|
|
/** @var Initializer $initializer */
|
|
$initializer = $discoveredAttribute->createAttributeInstance();
|
|
|
|
// The actual initializer data is in the additionalData from the InitializerMapper
|
|
$initializerData = $discoveredAttribute->additionalData;
|
|
|
|
if (! $initializerData) {
|
|
continue;
|
|
}
|
|
|
|
// Context-Filter: Prüfe ob Initializer für aktuellen Context erlaubt ist
|
|
// The contexts data is directly in the additionalData
|
|
// Wenn contexts null ist, ist der Initializer für alle Contexts verfügbar
|
|
if (
|
|
isset($initializerData['contexts'])
|
|
&& $initializerData['contexts'] !== null
|
|
&& ! $this->isContextAllowed(...$initializerData['contexts'])
|
|
) {
|
|
continue;
|
|
}
|
|
|
|
$methodName = $discoveredAttribute->methodName ?? MethodName::invoke();
|
|
// The return type is directly in the additionalData from the InitializerMapper
|
|
$returnType = $initializerData['return'] ?? null;
|
|
|
|
try {
|
|
// Setup-Initializer: void/null Return → Sofort ausführen
|
|
if ($returnType === null || $returnType === 'void') {
|
|
$this->container->invoker->invoke($discoveredAttribute->className, $methodName->toString());
|
|
}
|
|
// Service-Initializer: Konkreter Return-Type → Zum Dependency-Graph hinzufügen
|
|
else {
|
|
$dependencyGraph->addInitializer($returnType, $discoveredAttribute->className, $methodName);
|
|
}
|
|
} catch (\Throwable $e) {
|
|
$logger->error(
|
|
"Failed to register initializer: {$discoveredAttribute->className->toString()}::{$methodName->toString()}",
|
|
LogContext::withExceptionAndData($e, [
|
|
'class' => $discoveredAttribute->className->toString(),
|
|
'method' => $methodName->toString(),
|
|
'return_type' => $returnType,
|
|
])
|
|
);
|
|
|
|
// Skip failed initializers to prevent breaking the entire application
|
|
}
|
|
}
|
|
|
|
// Phase 2: Service-Initializer in optimaler Reihenfolge registrieren
|
|
$this->registerServicesWithDependencyGraph($dependencyGraph);
|
|
}
|
|
|
|
/**
|
|
* Registriert Services basierend auf Dependency-Graph in optimaler Reihenfolge
|
|
*/
|
|
private function registerServicesWithDependencyGraph(InitializerDependencyGraph $graph): void
|
|
{
|
|
try {
|
|
$executionOrder = $graph->getExecutionOrder();
|
|
|
|
foreach ($executionOrder as $returnType) {
|
|
if ($graph->hasNode($returnType)) {
|
|
/** @var DependencyGraphNode $node */
|
|
$node = $graph->getNode($returnType);
|
|
|
|
$this->registerLazyService(
|
|
$returnType,
|
|
$node->getClassName(),
|
|
$node->getMethodName()
|
|
);
|
|
}
|
|
}
|
|
} catch (\Throwable $e) {
|
|
// 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 */
|
|
foreach ($graph->getNodes() as $returnType => $node) {
|
|
$this->registerLazyService(
|
|
$returnType,
|
|
$node->getClassName(),
|
|
$node->getMethodName()
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Prüft ob ein Initializer im aktuellen Context ausgeführt werden darf
|
|
*/
|
|
private function isContextAllowed(mixed ...$contexts): bool
|
|
{
|
|
$currentContext = $this->executionContext->getType();
|
|
|
|
foreach ($contexts as $context) {
|
|
// Handle both ContextType objects and string values (from cache deserialization)
|
|
if ($context instanceof ContextType) {
|
|
if ($currentContext === $context) {
|
|
return true;
|
|
}
|
|
} elseif (is_string($context)) {
|
|
if ($currentContext->value === $context) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Registriert einen Service lazy im Container mit Dual-Registration für Interfaces
|
|
*/
|
|
private function registerLazyService(string $returnType, string $className, string $methodName): void
|
|
{
|
|
$factory = function ($container) use ($className, $methodName, $returnType) {
|
|
try {
|
|
$instance = $container->invoker->invoke(ClassName::create($className), $methodName);
|
|
} catch (\Throwable $e) {
|
|
$container->get(Logger::class)->error("Failed to invoke initializer method {$methodName} for class {$className}: {$e->getMessage()}");
|
|
throw $e;
|
|
}
|
|
|
|
// Wenn das ein Interface ist, registriere auch die konkrete Klasse automatisch
|
|
if (interface_exists($returnType)) {
|
|
$concreteClass = get_class($instance);
|
|
if (! $container->has($concreteClass)) {
|
|
$container->instance($concreteClass, $instance);
|
|
}
|
|
}
|
|
|
|
return $instance;
|
|
};
|
|
|
|
try {
|
|
// Registriere den Return-Type (Interface oder konkrete Klasse)
|
|
$this->container->singleton($returnType, $factory);
|
|
} catch (\Throwable $e) {
|
|
$logger = $this->getLogger();
|
|
$logger->error(
|
|
"Failed to register lazy service for return type: {$returnType}",
|
|
LogContext::withExceptionAndData($e, [
|
|
'return_type' => $returnType,
|
|
'class' => $className,
|
|
'method' => $methodName,
|
|
])
|
|
);
|
|
|
|
// 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);
|
|
}
|
|
}
|