feat(di): add proactive initializer finder for interface resolution

- Add ProactiveInitializerFinder to search for initializers when not found in registry
- Add InitializerInfo value object to store initializer metadata
- Implement multi-step search strategy: DefaultImplementation, naming convention, directory, subdirectories, module
- Integrate proactive finder into DefaultContainer for better interface resolution
- Simplify AppBootstrapper by moving initialization logic to DefaultContainer
- Improve error messages in ClassNotInstantiable with proactive finder context
This commit is contained in:
2025-11-03 17:45:47 +01:00
parent 8c264f3781
commit 9cad445aaf
7 changed files with 476 additions and 113 deletions

View File

@@ -8,6 +8,7 @@ use App\Framework\Core\ValueObjects\ClassName;
use App\Framework\DI\Exceptions\ClassNotInstantiable;
use App\Framework\DI\Exceptions\ClassNotResolvableException;
use App\Framework\DI\Exceptions\ClassResolutionException;
use App\Framework\DI\ProactiveInitializerFinder;
use App\Framework\DI\Exceptions\ContainerException;
use App\Framework\DI\Exceptions\CyclicDependencyException;
use App\Framework\DI\Exceptions\LazyLoadingException;
@@ -193,6 +194,12 @@ final class DefaultContainer implements Container
// Check if class is instantiable using the framework's method
if (! $reflection->isInstantiable()) {
// Proaktive Suche nach Initializern für Interfaces
if ($reflection->isInterface() && $this->tryFindAndRegisterInitializer($class)) {
// Initializer gefunden und registriert - versuche erneut zu resolven
return $this->get($class);
}
$this->throwDetailedBindingException($class);
}
@@ -217,6 +224,24 @@ final class DefaultContainer implements Container
{
$availableBindings = array_keys($this->bindings->getAllBindings());
// Bestimme den Typ der Klasse mit Reflection
$className = ClassName::create($class);
$isInterface = false;
$isAbstract = false;
$isTrait = false;
try {
$reflection = $this->reflectionProvider->getClass($className);
$isInterface = $reflection->isInterface();
$isAbstract = $reflection->isAbstract();
$isTrait = $reflection->isTrait();
} catch (\Throwable $e) {
// Fallback zu einfachen Prüfungen wenn Reflection fehlschlägt
$isInterface = interface_exists($class);
// Für Abstract und Trait können wir nicht so einfach prüfen, aber wir haben bereits
// festgestellt dass die Klasse nicht instanziierbar ist
}
// Try to get DiscoveryRegistry from container and include discovered initializers
$discoveredInitializers = [];
$matchingInitializers = [];
@@ -235,7 +260,7 @@ final class DefaultContainer implements Container
);
// Spezielle Behandlung für Interfaces: Suche nach Initializern die dieses Interface zurückgeben
if (interface_exists($class)) {
if ($isInterface) {
foreach ($initializerResults as $initializer) {
$returnType = $initializer->additionalData['return'] ?? null;
if ($returnType === $class) {
@@ -279,7 +304,10 @@ final class DefaultContainer implements Container
discoveredInitializers: $discoveredInitializers,
matchingInitializers: $matchingInitializers,
suggestedInitializer: $suggestedInitializer,
failedInitializer: $failedInitializer
failedInitializer: $failedInitializer,
isInterface: $isInterface,
isAbstract: $isAbstract,
isTrait: $isTrait
);
}
@@ -436,4 +464,70 @@ final class DefaultContainer implements Container
$this->instances->setSingleton(DefaultContainer::class, $this);
$this->instances->setSingleton(Container::class, $this);
}
/**
* Lazy-getter für ProactiveInitializerFinder
*/
private function getProactiveFinder(): ProactiveInitializerFinder
{
// Erstelle Finder nur wenn benötigt
$discoveryRegistry = $this->has(DiscoveryRegistry::class) ? $this->get(DiscoveryRegistry::class) : null;
if ($discoveryRegistry === null) {
// Fallback: Leere Registry erstellen
$discoveryRegistry = \App\Framework\Discovery\Results\DiscoveryRegistry::empty();
}
$fileScanner = $this->has(\App\Framework\Filesystem\FileScanner::class)
? $this->get(\App\Framework\Filesystem\FileScanner::class)
: new \App\Framework\Filesystem\FileScanner(null, null, new \App\Framework\Filesystem\FileSystemService());
$fileSystemService = new \App\Framework\Filesystem\FileSystemService();
$classExtractor = new \App\Framework\Discovery\Processing\ClassExtractor($fileSystemService);
$pathProvider = $this->has(\App\Framework\Core\PathProvider::class)
? $this->get(\App\Framework\Core\PathProvider::class)
: new \App\Framework\Core\PathProvider(getcwd() ?: '.');
return new ProactiveInitializerFinder(
reflectionProvider: $this->reflectionProvider,
discoveryRegistry: $discoveryRegistry,
fileScanner: $fileScanner,
classExtractor: $classExtractor,
pathProvider: $pathProvider
);
}
/**
* Versucht proaktiv einen Initializer für ein Interface zu finden und zu registrieren
*
* @param string $interface Interface-Klasse
* @return bool True wenn Initializer gefunden und registriert wurde, false sonst
*/
private function tryFindAndRegisterInitializer(string $interface): bool
{
try {
$finder = $this->getProactiveFinder();
$initializerInfo = $finder->findInitializerForInterface($interface);
if ($initializerInfo === null) {
return false;
}
// Registriere Initializer als lazy binding
$initializerClass = $initializerInfo->initializerClass->getFullyQualified();
$methodName = $initializerInfo->methodName->toString();
$this->singleton($interface, function (Container $container) use ($initializerClass, $methodName) {
$instance = $container->get($initializerClass);
return $container->invoker->invoke($initializerClass, $methodName);
});
// TODO: Gefundenen Initializer in Discovery Cache nachtragen (später implementieren)
return true;
} catch (\Throwable $e) {
// Silently ignore errors during proactive search
return false;
}
}
}