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:
@@ -11,16 +11,6 @@ use App\Framework\Performance\MemoryMonitor;
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
require __DIR__ . '/../src/Framework/Debug/helpers.php';
|
||||
|
||||
error_log('Starting application...');
|
||||
|
||||
error_log("------ ENVIRONMENT VARIABLES ------");
|
||||
|
||||
error_log(print_r($_ENV, true));
|
||||
|
||||
error_log("------ SERVER VARIABLES ------");
|
||||
|
||||
error_log(print_r($_SERVER, true));
|
||||
|
||||
// Anwendung initialisieren und ausführen
|
||||
$basePath = dirname(__DIR__);
|
||||
// Create dependencies for enhanced performance collector
|
||||
|
||||
@@ -59,62 +59,6 @@ final readonly class AppBootstrapper
|
||||
$env = $env->withVariable('REDIS_PASSWORD_FILE', $expectedRedisPasswordFile);
|
||||
}
|
||||
|
||||
error_log("------ ENVIRONMENT VARIABLES ------");
|
||||
error_log(print_r($env->all(true), true));
|
||||
error_log("------------------------------------");
|
||||
|
||||
// Debug: Check for specific Docker Secrets
|
||||
error_log("------ DOCKER SECRETS DEBUG ------");
|
||||
error_log("REDIS_PASSWORD_FILE exists: " . ($env->has('REDIS_PASSWORD_FILE') ? 'YES' : 'NO'));
|
||||
error_log("REDIS_PASSWORD_FILE value: " . ($env->get('REDIS_PASSWORD_FILE') ?? 'NOT SET'));
|
||||
error_log("REDIS_PASSWORD resolved: " . ($env->has('REDIS_PASSWORD') ? 'YES' : 'NO'));
|
||||
error_log("REDIS_PASSWORD value: " . (empty($env->get('REDIS_PASSWORD')) ? 'EMPTY' : 'SET'));
|
||||
|
||||
// Check system environment directly
|
||||
error_log("------ SYSTEM ENVIRONMENT CHECK ------");
|
||||
error_log("getenv('REDIS_PASSWORD_FILE'): " . (getenv('REDIS_PASSWORD_FILE') !== false ? getenv('REDIS_PASSWORD_FILE') : 'NOT SET'));
|
||||
error_log("isset(\$_ENV['REDIS_PASSWORD_FILE']): " . (isset($_ENV['REDIS_PASSWORD_FILE']) ? 'YES' : 'NO'));
|
||||
if (isset($_ENV['REDIS_PASSWORD_FILE'])) {
|
||||
error_log("\$_ENV['REDIS_PASSWORD_FILE']: " . $_ENV['REDIS_PASSWORD_FILE']);
|
||||
}
|
||||
error_log("isset(\$_SERVER['REDIS_PASSWORD_FILE']): " . (isset($_SERVER['REDIS_PASSWORD_FILE']) ? 'YES' : 'NO'));
|
||||
if (isset($_SERVER['REDIS_PASSWORD_FILE'])) {
|
||||
error_log("\$_SERVER['REDIS_PASSWORD_FILE']: " . $_SERVER['REDIS_PASSWORD_FILE']);
|
||||
}
|
||||
|
||||
// Check all *_FILE variables
|
||||
error_log("------ ALL *_FILE VARIABLES ------");
|
||||
$allEnvVars = getenv();
|
||||
if ($allEnvVars !== false) {
|
||||
foreach ($allEnvVars as $key => $value) {
|
||||
if (str_ends_with($key, '_FILE')) {
|
||||
error_log("$key: $value");
|
||||
}
|
||||
}
|
||||
}
|
||||
error_log("------ \$_ENV *_FILE VARIABLES ------");
|
||||
foreach ($_ENV as $key => $value) {
|
||||
if (is_string($key) && str_ends_with($key, '_FILE')) {
|
||||
error_log("$key: $value");
|
||||
}
|
||||
}
|
||||
|
||||
// Check if file exists at expected location
|
||||
$expectedFile = '/run/secrets/redis_password';
|
||||
error_log("Expected file path: $expectedFile");
|
||||
error_log("File exists: " . (file_exists($expectedFile) ? 'YES' : 'NO'));
|
||||
error_log("File readable: " . (is_readable($expectedFile) ? 'YES' : 'NO'));
|
||||
if (file_exists($expectedFile)) {
|
||||
error_log("File permissions: " . substr(sprintf('%o', fileperms($expectedFile)), -4));
|
||||
error_log("File owner: " . fileowner($expectedFile));
|
||||
error_log("Current process user: " . getmyuid());
|
||||
if (is_readable($expectedFile)) {
|
||||
$content = file_get_contents($expectedFile);
|
||||
error_log("File content length: " . strlen($content ?? ''));
|
||||
}
|
||||
}
|
||||
error_log("------------------------------------");
|
||||
|
||||
// Make Environment available throughout the application
|
||||
$this->container->instance(Environment::class, $env);
|
||||
|
||||
@@ -129,30 +73,6 @@ final readonly class AppBootstrapper
|
||||
// Register MemoryMonitor as singleton
|
||||
$this->container->singleton(MemoryMonitor::class, $this->memoryMonitor);
|
||||
|
||||
|
||||
|
||||
// Only log context in development - production doesn't need this noise
|
||||
//$envType = EnvironmentType::fromEnvironment($env);
|
||||
|
||||
//if ($envType->isDevelopment()) {
|
||||
|
||||
/*if($typedConfig->app->type->isDevelopment()) {
|
||||
// Fehleranzeige für die Entwicklung aktivieren
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('display_startup_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
spl_autoload_register(function ($class) {
|
||||
if (empty($class)) {
|
||||
error_log('Empty class name detected in autoloader. Stack trace: ' .
|
||||
json_encode(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 10)));
|
||||
|
||||
return false; // Don't throw, just log and continue
|
||||
}
|
||||
|
||||
return false;
|
||||
}, true, true);
|
||||
}*/
|
||||
}
|
||||
|
||||
public function bootstrapWeb(): ApplicationInterface
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,9 @@ final class ClassNotInstantiable extends ContainerException
|
||||
* @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
|
||||
* @param bool $isInterface Ob die Klasse ein Interface ist
|
||||
* @param bool $isAbstract Ob die Klasse abstract ist
|
||||
* @param bool $isTrait Ob die Klasse ein Trait ist
|
||||
*/
|
||||
public static function fromContainerContext(
|
||||
string $class,
|
||||
@@ -30,7 +33,10 @@ final class ClassNotInstantiable extends ContainerException
|
||||
array $discoveredInitializers = [],
|
||||
array $matchingInitializers = [],
|
||||
?string $suggestedInitializer = null,
|
||||
?FailedInitializer $failedInitializer = null
|
||||
?FailedInitializer $failedInitializer = null,
|
||||
bool $isInterface = false,
|
||||
bool $isAbstract = false,
|
||||
bool $isTrait = false
|
||||
): self {
|
||||
// Calculate similar bindings based on class name
|
||||
$similarBindings = array_filter($availableBindings, function ($binding) use ($class) {
|
||||
@@ -45,7 +51,10 @@ final class ClassNotInstantiable extends ContainerException
|
||||
discoveredInitializers: $discoveredInitializers,
|
||||
matchingInitializers: $matchingInitializers,
|
||||
suggestedInitializer: $suggestedInitializer,
|
||||
failedInitializer: $failedInitializer
|
||||
failedInitializer: $failedInitializer,
|
||||
isInterface: $isInterface,
|
||||
isAbstract: $isAbstract,
|
||||
isTrait: $isTrait
|
||||
);
|
||||
}
|
||||
|
||||
@@ -66,52 +75,65 @@ final class ClassNotInstantiable extends ContainerException
|
||||
public readonly array $matchingInitializers = [],
|
||||
public readonly ?string $suggestedInitializer = null,
|
||||
public readonly ?FailedInitializer $failedInitializer = null,
|
||||
public readonly bool $isInterface = false,
|
||||
public readonly bool $isAbstract = false,
|
||||
public readonly bool $isTrait = false,
|
||||
)
|
||||
{
|
||||
$dependencyChainStr = implode(' -> ', $this->dependencyChain);
|
||||
$isInterface = interface_exists($this->class);
|
||||
|
||||
$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";
|
||||
// Spezifische Fehlermeldungen je nach Typ
|
||||
if ($this->isInterface) {
|
||||
$message = "Cannot instantiate interface '{$this->class}': interfaces cannot be instantiated directly.\n";
|
||||
$message .= "An initializer must provide an implementation.\n\n";
|
||||
|
||||
$message .= "Dependency resolution chain: {$dependencyChainStr}\n";
|
||||
|
||||
// Spezielle Behandlung für Interfaces
|
||||
if ($isInterface) {
|
||||
// Matching Initializer
|
||||
// IMMER Initializer-Informationen anzeigen
|
||||
if (! empty($this->matchingInitializers)) {
|
||||
$message .= "\nFound " . count($this->matchingInitializers) . " initializer(s) that return this interface:\n";
|
||||
$message .= "Found " . count($this->matchingInitializers) . " initializer(s) that return this interface:\n";
|
||||
foreach ($this->matchingInitializers as $matching) {
|
||||
$message .= " - {$matching}\n";
|
||||
}
|
||||
$message .= "\n";
|
||||
} else {
|
||||
$message .= "No initializers found that return this interface.\n\n";
|
||||
}
|
||||
|
||||
// Fehlgeschlagener Initializer
|
||||
if ($this->failedInitializer !== null) {
|
||||
$message .= "\n⚠️ Initializer found but failed during execution:\n";
|
||||
$message .= "⚠️ 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";
|
||||
$message .= " - Check logs for full stack trace\n\n";
|
||||
} else {
|
||||
// Vorgeschlagener Initializer
|
||||
if ($this->suggestedInitializer !== null) {
|
||||
$message .= "\nSuggested initializer (based on interface name):\n";
|
||||
$message .= " - {$this->suggestedInitializer}\n";
|
||||
$message .= "Suggested initializer (based on interface name):\n";
|
||||
$message .= " - {$this->suggestedInitializer}\n\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";
|
||||
if (empty($this->matchingInitializers)) {
|
||||
$message .= "If 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 .= " - Ensure the initializer is registered with #[Initializer] attribute\n\n";
|
||||
}
|
||||
}
|
||||
} elseif ($this->isAbstract) {
|
||||
$message = "Cannot instantiate abstract class '{$this->class}': abstract classes should not be used in the framework.\n";
|
||||
$message .= "Use concrete classes or interfaces instead.\n\n";
|
||||
} elseif ($this->isTrait) {
|
||||
$message = "Cannot instantiate trait '{$this->class}': traits cannot be instantiated directly.\n";
|
||||
$message .= "Traits must be used by classes.\n\n";
|
||||
} else {
|
||||
// Fallback für unbekannte Fälle
|
||||
$message = "Cannot instantiate class '{$this->class}': class is not instantiable (interface, abstract class, or trait).\n\n";
|
||||
}
|
||||
|
||||
$message .= "Dependency resolution chain: {$dependencyChainStr}\n";
|
||||
|
||||
$message .= "\nTotal available bindings: " . count($this->availableBindings) . "\n";
|
||||
|
||||
if (!empty($this->similarBindings)) {
|
||||
|
||||
316
src/Framework/DI/ProactiveInitializerFinder.php
Normal file
316
src/Framework/DI/ProactiveInitializerFinder.php
Normal file
@@ -0,0 +1,316 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\DI;
|
||||
|
||||
use App\Framework\Core\PathProvider;
|
||||
use App\Framework\Core\ValueObjects\ClassName;
|
||||
use App\Framework\Core\ValueObjects\MethodName;
|
||||
use App\Framework\Core\ValueObjects\PhpNamespace;
|
||||
use App\Framework\DI\Attributes\DefaultImplementation;
|
||||
use App\Framework\DI\ValueObjects\InitializerInfo;
|
||||
use App\Framework\Discovery\Processing\ClassExtractor;
|
||||
use App\Framework\Discovery\Results\DiscoveryRegistry;
|
||||
use App\Framework\Filesystem\File;
|
||||
use App\Framework\Filesystem\FileScanner;
|
||||
use App\Framework\Filesystem\ValueObjects\FilePath;
|
||||
use App\Framework\Filesystem\ValueObjects\FilePattern;
|
||||
use App\Framework\Reflection\ReflectionProvider;
|
||||
|
||||
/**
|
||||
* Proaktive Suche nach Initializern für Interfaces
|
||||
*
|
||||
* Wenn kein Initializer in der Discovery Registry gefunden wird, sucht diese Klasse
|
||||
* proaktiv nach Initializern basierend auf Namenskonventionen, Verzeichnisstruktur,
|
||||
* Modulen und DefaultImplementation Attributen.
|
||||
*/
|
||||
final readonly class ProactiveInitializerFinder
|
||||
{
|
||||
public function __construct(
|
||||
private ReflectionProvider $reflectionProvider,
|
||||
private DiscoveryRegistry $discoveryRegistry,
|
||||
private FileScanner $fileScanner,
|
||||
private ClassExtractor $classExtractor,
|
||||
private PathProvider $pathProvider
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Findet einen Initializer für ein Interface
|
||||
*
|
||||
* Suchreihenfolge:
|
||||
* 1. DefaultImplementation Attribut
|
||||
* 2. Namenskonvention
|
||||
* 3. Selber Ordner
|
||||
* 4. Unterordner
|
||||
* 5. Ganzes Modul
|
||||
*/
|
||||
public function findInitializerForInterface(string $interface): ?InitializerInfo
|
||||
{
|
||||
// 1. DefaultImplementation Attribut (ERSTE Suche)
|
||||
$result = $this->findByDefaultImplementation($interface);
|
||||
if ($result !== null) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
// 2. Namenskonvention
|
||||
$result = $this->findByName($interface);
|
||||
if ($result !== null) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
// 3. Selber Ordner
|
||||
$result = $this->findInDirectory($interface);
|
||||
if ($result !== null) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
// 4. Unterordner
|
||||
$result = $this->findInSubdirectories($interface);
|
||||
if ($result !== null) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
// 5. Ganzes Modul
|
||||
return $this->findInModule($interface);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sucht nach Initializer basierend auf DefaultImplementation Attribut
|
||||
*/
|
||||
private function findByDefaultImplementation(string $interface): ?InitializerInfo
|
||||
{
|
||||
try {
|
||||
// Suche in DiscoveryRegistry nach DefaultImplementation Attributen
|
||||
$defaultImplResults = $this->discoveryRegistry->attributes->get(DefaultImplementation::class);
|
||||
|
||||
if (empty($defaultImplResults)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($defaultImplResults as $defaultImpl) {
|
||||
$implClassName = $defaultImpl->className;
|
||||
|
||||
// Prüfe ob das Attribut das richtige Interface spezifiziert
|
||||
$defaultImplAttr = $defaultImpl->createAttributeInstance();
|
||||
if ($defaultImplAttr instanceof DefaultImplementation) {
|
||||
// Wenn Interface explizit angegeben, muss es übereinstimmen
|
||||
if ($defaultImplAttr->interface !== null && $defaultImplAttr->interface !== $interface) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Prüfe ob die Klasse das Interface implementiert
|
||||
$reflection = $this->reflectionProvider->getClass($implClassName);
|
||||
|
||||
if (!$reflection->implementsInterface($interface)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Prüfe ob die Klasse auch Initializer Attribut hat
|
||||
$initializerInfo = $this->checkInitializerAttribute($implClassName, $interface);
|
||||
if ($initializerInfo !== null) {
|
||||
return $initializerInfo;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// Silently ignore errors
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sucht nach Initializer basierend auf Namenskonvention
|
||||
* SomeClassInterface -> SomeClassInitializer
|
||||
*/
|
||||
private function findByName(string $interface): ?InitializerInfo
|
||||
{
|
||||
try {
|
||||
$interfaceName = ClassName::create($interface);
|
||||
$shortName = $interfaceName->getShortName();
|
||||
|
||||
// Entferne "Interface" Suffix
|
||||
if (!str_ends_with($shortName, 'Interface')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$baseName = substr($shortName, 0, -9); // "Interface" = 9 Zeichen
|
||||
$initializerName = $baseName . 'Initializer';
|
||||
|
||||
// Konstruiere vollständigen Klassennamen
|
||||
$namespace = $interfaceName->getNamespaceObject();
|
||||
$initializerClassName = ClassName::fromNamespace($namespace, $initializerName);
|
||||
|
||||
// Prüfe ob Klasse existiert
|
||||
if (!$initializerClassName->exists()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Prüfe Initializer Attribut
|
||||
return $this->checkInitializerAttribute($initializerClassName, $interface);
|
||||
} catch (\Throwable $e) {
|
||||
// Silently ignore errors
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sucht nach Initializer im selben Ordner wie das Interface
|
||||
*/
|
||||
private function findInDirectory(string $interface): ?InitializerInfo
|
||||
{
|
||||
try {
|
||||
$interfaceName = ClassName::create($interface);
|
||||
$namespace = $interfaceName->getNamespaceObject();
|
||||
|
||||
$directoryPath = $this->namespaceToDirectoryPath($namespace);
|
||||
if ($directoryPath === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->searchInPath(FilePath::create($directoryPath), $interface, false);
|
||||
} catch (\Throwable $e) {
|
||||
// Silently ignore errors
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sucht nach Initializer in Unterordnern des Interface-Ordners
|
||||
*/
|
||||
private function findInSubdirectories(string $interface): ?InitializerInfo
|
||||
{
|
||||
try {
|
||||
$interfaceName = ClassName::create($interface);
|
||||
$namespace = $interfaceName->getNamespaceObject();
|
||||
|
||||
$directoryPath = $this->namespaceToDirectoryPath($namespace);
|
||||
if ($directoryPath === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->searchInPath(FilePath::create($directoryPath), $interface, true);
|
||||
} catch (\Throwable $e) {
|
||||
// Silently ignore errors
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sucht nach Initializer im ganzen Modul
|
||||
*/
|
||||
private function findInModule(string $interface): ?InitializerInfo
|
||||
{
|
||||
try {
|
||||
$interfaceName = ClassName::create($interface);
|
||||
$namespace = $interfaceName->getNamespaceObject();
|
||||
|
||||
// Bestimme Modul-Namespace (Parent-Namespace)
|
||||
$moduleNamespace = $namespace->parent();
|
||||
if ($moduleNamespace === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$directoryPath = $this->namespaceToDirectoryPath($moduleNamespace);
|
||||
if ($directoryPath === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->searchInPath(FilePath::create($directoryPath), $interface, true);
|
||||
} catch (\Throwable $e) {
|
||||
// Silently ignore errors
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Durchsucht einen Pfad nach Initializern
|
||||
*/
|
||||
private function searchInPath(FilePath $path, string $interface, bool $recursive): ?InitializerInfo
|
||||
{
|
||||
try {
|
||||
foreach ($this->fileScanner->streamFiles($path, FilePattern::php()) as $file) {
|
||||
$classes = $this->classExtractor->extractFromFile($file);
|
||||
|
||||
foreach ($classes as $className) {
|
||||
if (!$className->exists()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$initializerInfo = $this->checkInitializerAttribute($className, $interface);
|
||||
if ($initializerInfo !== null) {
|
||||
return $initializerInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// Silently ignore errors
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft ob eine Klasse ein Initializer Attribut hat und das Interface zurückgibt
|
||||
*/
|
||||
private function checkInitializerAttribute(ClassName $className, string $interface): ?InitializerInfo
|
||||
{
|
||||
try {
|
||||
$reflection = $this->reflectionProvider->getClass($className);
|
||||
$methods = $reflection->getMethods();
|
||||
|
||||
foreach ($methods as $method) {
|
||||
// Prüfe ob Methode Initializer Attribut hat
|
||||
if (!$method->hasAttribute(Initializer::class)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Prüfe Return-Type
|
||||
$returnType = $method->getReturnType();
|
||||
if ($returnType === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Normalisiere Return-Type (entferne leading backslash, handle nullable)
|
||||
$returnType = ltrim($returnType, '\\?');
|
||||
|
||||
// Prüfe ob Return-Type das Interface ist
|
||||
if ($returnType !== $interface && $returnType !== '\\' . $interface) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Gefunden!
|
||||
return new InitializerInfo(
|
||||
initializerClass: $className,
|
||||
methodName: MethodName::create($method->getName()),
|
||||
returnType: ClassName::create($interface)
|
||||
);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// Silently ignore errors
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Konvertiert einen PhpNamespace in ein Verzeichnis
|
||||
*/
|
||||
private function namespaceToDirectoryPath(PhpNamespace $namespace): ?string
|
||||
{
|
||||
// Nutze PathProvider um Namespace zu Pfad zu konvertieren
|
||||
$filePath = $this->pathProvider->namespaceToPath($namespace);
|
||||
if ($filePath === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Entferne .php Extension und Dateiname - wir wollen das Verzeichnis
|
||||
return dirname($filePath);
|
||||
}
|
||||
}
|
||||
|
||||
21
src/Framework/DI/ValueObjects/InitializerInfo.php
Normal file
21
src/Framework/DI/ValueObjects/InitializerInfo.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\DI\ValueObjects;
|
||||
|
||||
use App\Framework\Core\ValueObjects\ClassName;
|
||||
use App\Framework\Core\ValueObjects\MethodName;
|
||||
|
||||
/**
|
||||
* Value Object für gefundene Initializer-Informationen
|
||||
*/
|
||||
final readonly class InitializerInfo
|
||||
{
|
||||
public function __construct(
|
||||
public ClassName $initializerClass,
|
||||
public MethodName $methodName,
|
||||
public ClassName $returnType
|
||||
) {}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ final readonly class InitializerProcessor
|
||||
$logger->debug("InitializerProcessor: Processing " . count($initializerResults) . " initializers");
|
||||
|
||||
$dependencyGraph = new InitializerDependencyGraph($this->reflectionProvider);
|
||||
/** @var FailedInitializer[] */
|
||||
/** @var FailedInitializer[] $failedInitializers */
|
||||
$failedInitializers = [];
|
||||
|
||||
// Phase 1: Setup-Initializer sofort ausführen & Service-Initializer zum Graph hinzufügen
|
||||
|
||||
Reference in New Issue
Block a user