chore: complete update
This commit is contained in:
79
src/Framework/DI/AttributeProcessorRegistry.php
Normal file
79
src/Framework/DI/AttributeProcessorRegistry.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\DI;
|
||||
|
||||
use App\Framework\Core\AttributeCompiler;
|
||||
use App\Framework\Core\AttributeMapper;
|
||||
|
||||
final class AttributeProcessorRegistry
|
||||
{
|
||||
/**
|
||||
* @var AttributeMapper[]
|
||||
*/
|
||||
private array $mappers = [];
|
||||
|
||||
/**
|
||||
* @var AttributeCompiler[]
|
||||
*/
|
||||
private array $compilers = [];
|
||||
|
||||
public function __construct(private readonly DefaultContainer $container)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Registriert einen AttributeMapper
|
||||
*/
|
||||
public function registerMapper(string $mapperClass): self
|
||||
{
|
||||
$this->mappers[] = $mapperClass;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registriert einen AttributeCompiler
|
||||
*/
|
||||
public function registerCompiler(string $compilerClass): self
|
||||
{
|
||||
$this->compilers[] = $compilerClass;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verarbeitet alle Attribute mit den registrierten Mappern und Compilern
|
||||
*/
|
||||
public function processAll(array $discoveryResults): array
|
||||
{
|
||||
$compiledResults = [];
|
||||
$mappedByType = [];
|
||||
|
||||
// Mapper-Instanzen erstellen
|
||||
$mapperInstances = [];
|
||||
foreach ($this->mappers as $mapperClass) {
|
||||
$mapper = $this->container->get($mapperClass);
|
||||
$mapperInstances[$mapper->getAttributeClass()] = $mapper;
|
||||
}
|
||||
|
||||
// Compiler-Instanzen erstellen
|
||||
$compilerInstances = [];
|
||||
foreach ($this->compilers as $compilerClass) {
|
||||
$compiler = $this->container->get($compilerClass);
|
||||
$compilerInstances[$compiler->getAttributeClass()] = $compiler;
|
||||
}
|
||||
|
||||
// Attribute kompilieren, für die sowohl Mapper als auch Compiler existieren
|
||||
foreach ($discoveryResults as $attributeClass => $attributeData) {
|
||||
if (isset($mapperInstances[$attributeClass]) && isset($compilerInstances[$attributeClass])) {
|
||||
// Wenn ein Compiler vorhanden ist, verwenden wir ihn zur Kompilierung
|
||||
$compiledResults[$attributeClass] = $compilerInstances[$attributeClass]->compile($attributeData);
|
||||
} elseif (isset($mapperInstances[$attributeClass])) {
|
||||
// Ohne Compiler geben wir die gemappten Daten direkt zurück
|
||||
$compiledResults[$attributeClass] = $attributeData;
|
||||
}
|
||||
}
|
||||
|
||||
return $compiledResults;
|
||||
}
|
||||
|
||||
}
|
||||
36
src/Framework/DI/BindingRegistry.php
Normal file
36
src/Framework/DI/BindingRegistry.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Framework\DI;
|
||||
|
||||
/**
|
||||
* Verwaltet alle Service-Bindings
|
||||
*/
|
||||
final class BindingRegistry
|
||||
{
|
||||
private array $bindings = [];
|
||||
|
||||
public function bind(string $abstract, callable|string|object $concrete): void
|
||||
{
|
||||
$this->bindings[$abstract] = $concrete;
|
||||
}
|
||||
|
||||
public function hasBinding(string $abstract): bool
|
||||
{
|
||||
return isset($this->bindings[$abstract]);
|
||||
}
|
||||
|
||||
public function getBinding(string $abstract): callable|string|object|null
|
||||
{
|
||||
return $this->bindings[$abstract] ?? null;
|
||||
}
|
||||
|
||||
public function forget(string $abstract): void
|
||||
{
|
||||
unset($this->bindings[$abstract]);
|
||||
}
|
||||
|
||||
public function getAllBindings(): array
|
||||
{
|
||||
return array_keys($this->bindings);
|
||||
}
|
||||
}
|
||||
@@ -1,47 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\DI;
|
||||
|
||||
use App\Framework\Attributes\Singleton;
|
||||
|
||||
class Container
|
||||
interface Container
|
||||
{
|
||||
private array $singletons = [];
|
||||
public MethodInvoker $invoker {get;}
|
||||
|
||||
public function __construct(private array $definitions)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function get(string $class): object
|
||||
{
|
||||
if (isset($this->singletons[$class])) {
|
||||
return $this->singletons[$class];
|
||||
}
|
||||
|
||||
$reflection = new \ReflectionClass($class);
|
||||
|
||||
$constructor = $reflection->getConstructor();
|
||||
$dependencies = [];
|
||||
|
||||
if ($constructor !== null) {
|
||||
foreach ($constructor->getParameters() as $param) {
|
||||
$type = $param->getType();
|
||||
if (!$type || $type->isBuiltin()) {
|
||||
throw new \RuntimeException("Cannot resolve parameter {$param->getName()}");
|
||||
}
|
||||
$dependencies[] = $this->get($type->getName());
|
||||
}
|
||||
}
|
||||
|
||||
$instance = $reflection->newInstanceArgs($dependencies);
|
||||
|
||||
if ($reflection->getAttributes(Singleton::class)) {
|
||||
$this->singletons[$class] = $instance;
|
||||
}
|
||||
|
||||
return $instance;
|
||||
}
|
||||
/**
|
||||
* @template T of object
|
||||
* @param class-string<T> $class
|
||||
* @return T
|
||||
*/
|
||||
public function get(string $class): object;
|
||||
public function has(string $class): bool;
|
||||
public function bind(string $abstract, callable|string|object $concrete): void;
|
||||
public function singleton(string $abstract, callable|string|object $concrete): void;
|
||||
public function instance(string $abstract, object $instance): void;
|
||||
public function forget(string $class): void;
|
||||
}
|
||||
|
||||
142
src/Framework/DI/ContainerCompiler.php
Normal file
142
src/Framework/DI/ContainerCompiler.php
Normal file
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
|
||||
namespace App\Framework\DI;
|
||||
|
||||
class ContainerCompiler
|
||||
{
|
||||
public function compile(DefaultContainer $container, array $services, string $outputFile): void
|
||||
{
|
||||
$code = "<?php\n\nnamespace App\\Framework\\DI;\n\nclass CompiledContainer extends Container\n{\n";
|
||||
|
||||
// get()-Methode überschreiben
|
||||
$code .= " public function get(string \$class): object\n {\n";
|
||||
$code .= " // Singleton- und Instanz-Prüfung vom Basiscontainer\n";
|
||||
$code .= " if (isset(\$this->singletons[\$class])) {\n";
|
||||
$code .= " return \$this->singletons[\$class];\n";
|
||||
$code .= " }\n\n";
|
||||
$code .= " if (isset(\$this->instances[\$class])) {\n";
|
||||
$code .= " return \$this->instances[\$class];\n";
|
||||
$code .= " }\n\n";
|
||||
|
||||
// Switch-Statement für bekannte Services
|
||||
$code .= " // Optimierte Factory-Methoden für bekannte Services\n";
|
||||
$code .= " switch (\$class) {\n";
|
||||
|
||||
foreach ($services as $service) {
|
||||
$code .= " case \\{$service}::class:\n";
|
||||
$code .= " return \$this->create" . $this->getServiceMethodName($service) . "();\n";
|
||||
}
|
||||
|
||||
$code .= " default:\n";
|
||||
$code .= " // Fallback auf Standard-Container\n";
|
||||
$code .= " return parent::get(\$class);\n";
|
||||
$code .= " }\n";
|
||||
$code .= " }\n\n";
|
||||
|
||||
// Factory-Methoden für jeden bekannten Service
|
||||
foreach ($services as $service) {
|
||||
$code .= $this->generateFactoryMethod($service, $container);
|
||||
}
|
||||
|
||||
$code .= "}\n";
|
||||
|
||||
file_put_contents($outputFile, $code);
|
||||
}
|
||||
|
||||
private function getServiceMethodName(string $service): string
|
||||
{
|
||||
// Erzeugt einen methodenkompatiblen Namen aus dem Klassennamen
|
||||
$parts = explode('\\', $service);
|
||||
return end($parts);
|
||||
}
|
||||
|
||||
private function generateFactoryMethod(string $service, DefaultContainer $container): string
|
||||
{
|
||||
// Analysiert den Service und seine Abhängigkeiten
|
||||
$reflection = new \ReflectionClass($service);
|
||||
$constructor = $reflection->getConstructor();
|
||||
|
||||
$methodName = 'create' . $this->getServiceMethodName($service);
|
||||
$code = " private function {$methodName}(): \\{$service}\n {\n";
|
||||
|
||||
// Dependency-Analyse und optimierter Instantiierungs-Code
|
||||
if ($constructor) {
|
||||
$params = [];
|
||||
foreach ($constructor->getParameters() as $param) {
|
||||
$paramType = $param->getType();
|
||||
if ($paramType instanceof \ReflectionNamedType && !$paramType->isBuiltin()) {
|
||||
$typeName = $paramType->getName();
|
||||
$params[] = "\$this->get(\\{$typeName}::class)";
|
||||
} elseif ($param->isDefaultValueAvailable()) {
|
||||
$default = var_export($param->getDefaultValue(), true);
|
||||
$params[] = $default;
|
||||
} elseif ($paramType instanceof \ReflectionNamedType && $paramType->allowsNull()) {
|
||||
$params[] = 'null';
|
||||
} else {
|
||||
// Nicht auflösbar, Fallback
|
||||
$paramName = $param->getName();
|
||||
$params[] = "/* Parameter '{$paramName}' konnte nicht aufgelöst werden */";
|
||||
}
|
||||
}
|
||||
|
||||
$paramsStr = implode(', ', $params);
|
||||
$code .= " return new \\{$service}({$paramsStr});\n";
|
||||
} else {
|
||||
$code .= " return new \\{$service}();\n";
|
||||
}
|
||||
|
||||
$code .= " }\n\n";
|
||||
|
||||
return $code;
|
||||
}
|
||||
|
||||
|
||||
// Neue Methode zur Abhängigkeitsanalyse hinzufügen
|
||||
public function analyzeDependencies(DefaultContainer $container, array $services): array
|
||||
{
|
||||
$dependencies = [];
|
||||
$analyzedServices = [];
|
||||
|
||||
// Rekursive Analyse von Service-Abhängigkeiten
|
||||
foreach ($services as $service) {
|
||||
$this->analyzeServiceDependencies($service, $dependencies, $analyzedServices);
|
||||
}
|
||||
|
||||
return array_unique(array_merge($services, array_keys($dependencies)));
|
||||
}
|
||||
|
||||
private function analyzeServiceDependencies(
|
||||
string $service,
|
||||
array &$dependencies,
|
||||
array &$analyzedServices
|
||||
): void {
|
||||
// Vermeidung von Zyklen
|
||||
if (isset($analyzedServices[$service])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$analyzedServices[$service] = true;
|
||||
|
||||
try {
|
||||
$reflection = new \ReflectionClass($service);
|
||||
$constructor = $reflection->getConstructor();
|
||||
|
||||
if ($constructor) {
|
||||
foreach ($constructor->getParameters() as $param) {
|
||||
$type = $param->getType();
|
||||
|
||||
if ($type instanceof \ReflectionNamedType && !$type->isBuiltin()) {
|
||||
$typeName = $type->getName();
|
||||
$dependencies[$typeName] = true;
|
||||
|
||||
// Rekursive Analyse der Abhängigkeit
|
||||
$this->analyzeServiceDependencies($typeName, $dependencies, $analyzedServices);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (\ReflectionException $e) {
|
||||
// Fehlerbehandlung
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
90
src/Framework/DI/DOKUMENTATION.md
Normal file
90
src/Framework/DI/DOKUMENTATION.md
Normal file
@@ -0,0 +1,90 @@
|
||||
# Dependency Injection Modul
|
||||
|
||||
## Übersicht
|
||||
|
||||
Das DI-Modul implementiert ein Dependency-Injection-Container-System, das die automatische Auflösung und Verwaltung von Abhängigkeiten ermöglicht.
|
||||
|
||||
## Hauptkomponenten
|
||||
|
||||
### Container
|
||||
|
||||
Der zentrale Service-Container, der Instanzen erstellt und verwaltet.
|
||||
|
||||
**Hauptfunktionen:**
|
||||
- Service-Erstellung und -Auflösung
|
||||
- Singleton-Verwaltung
|
||||
- Rekursive Abhängigkeitsauflösung
|
||||
|
||||
### Initializer-Attribut
|
||||
|
||||
Das `#[Initializer]`-Attribut kennzeichnet Klassen, die Services im Container registrieren können.
|
||||
|
||||
**Beispiel:**
|
||||
```php
|
||||
readonly class AnalyticsInitializer
|
||||
{
|
||||
#[Initializer]
|
||||
public function __invoke(Container $container): Analytics
|
||||
{
|
||||
// Service erstellen und konfigurieren
|
||||
return $analytics;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Verwendung
|
||||
|
||||
### Service definieren
|
||||
|
||||
```php
|
||||
// Service-Interface
|
||||
interface MyServiceInterface
|
||||
{
|
||||
public function doSomething(): void;
|
||||
}
|
||||
|
||||
// Konkrete Implementierung
|
||||
class MyService implements MyServiceInterface
|
||||
{
|
||||
public function doSomething(): void
|
||||
{
|
||||
// Implementierung
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Service registrieren
|
||||
|
||||
```php
|
||||
|
||||
class MyServiceInitializer
|
||||
{
|
||||
#[Initializer]
|
||||
public function __invoke(Container $container): MyServiceInterface
|
||||
{
|
||||
return new MyService();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Service verwenden
|
||||
|
||||
```php
|
||||
class MyController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly MyServiceInterface $myService
|
||||
) {}
|
||||
|
||||
public function action(): void
|
||||
{
|
||||
$this->myService->doSomething();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Prinzipien
|
||||
|
||||
- **Automatische Auflösung**: Abhängigkeiten werden anhand von Typ-Hints automatisch aufgelöst
|
||||
- **Lazy Loading**: Services werden erst erstellt, wenn sie benötigt werden
|
||||
- **Singleton-Modus**: Standardmäßig werden Services als Singletons verwaltet
|
||||
177
src/Framework/DI/DefaultContainer.php
Normal file
177
src/Framework/DI/DefaultContainer.php
Normal file
@@ -0,0 +1,177 @@
|
||||
<?php
|
||||
|
||||
namespace App\Framework\DI;
|
||||
|
||||
use App\Framework\DI\Exceptions\CyclicDependencyException;
|
||||
use App\Framework\DI\Exceptions\LazyLoadingException;
|
||||
use App\Framework\Reflection\CachedReflectionProvider;
|
||||
use App\Framework\Reflection\ReflectionProvider;
|
||||
|
||||
use Throwable;
|
||||
|
||||
final class DefaultContainer implements Container
|
||||
{
|
||||
private array $resolving = [];
|
||||
private readonly DependencyResolver $dependencyResolver;
|
||||
private readonly SingletonDetector $singletonDetector;
|
||||
private readonly LazyInstantiator $lazyInstantiator;
|
||||
public readonly MethodInvoker $invoker;
|
||||
|
||||
public function __construct(
|
||||
private readonly InstanceRegistry $instances = new InstanceRegistry(),
|
||||
private readonly BindingRegistry $bindings = new BindingRegistry(),
|
||||
private readonly ReflectionProvider $reflectionProvider = new CachedReflectionProvider(),
|
||||
){
|
||||
$this->dependencyResolver = new DependencyResolver($this->reflectionProvider, $this);
|
||||
$this->singletonDetector = new SingletonDetector($this->reflectionProvider, $this->instances);
|
||||
$this->lazyInstantiator = new LazyInstantiator(
|
||||
$this->reflectionProvider,
|
||||
$this->createInstance(...)
|
||||
);
|
||||
$this->invoker = new MethodInvoker($this, $this->reflectionProvider);
|
||||
|
||||
$this->registerSelf();
|
||||
}
|
||||
|
||||
public function bind(string $abstract, callable|string|object $concrete): void
|
||||
{
|
||||
$this->bindings->bind($abstract, $concrete);
|
||||
$this->clearCaches($abstract);
|
||||
}
|
||||
|
||||
public function singleton(string $abstract, callable|string|object $concrete): void
|
||||
{
|
||||
$this->bind($abstract, $concrete);
|
||||
$this->instances->markAsSingleton($abstract);
|
||||
}
|
||||
|
||||
public function instance(string $abstract, object $instance): void
|
||||
{
|
||||
$this->instances->setInstance($abstract, $instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T of object
|
||||
* @param class-string<T> $class
|
||||
* @return T
|
||||
*/
|
||||
public function get(string $class): object
|
||||
{
|
||||
// Bereits instanziierte Objekte zurückgeben
|
||||
if ($this->instances->hasSingleton($class)) {
|
||||
return $this->instances->getSingleton($class);
|
||||
}
|
||||
|
||||
if ($this->instances->hasInstance($class)) {
|
||||
return $this->instances->getInstance($class);
|
||||
}
|
||||
|
||||
// Lazy Loading versuchen
|
||||
if ($this->lazyInstantiator->canUseLazyLoading($class, $this->instances)) {
|
||||
try {
|
||||
return $this->lazyInstantiator->createLazyInstance($class);
|
||||
} catch (Throwable $e) {
|
||||
if (!$e instanceof LazyLoadingException) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->createInstance($class);
|
||||
}
|
||||
|
||||
private function createInstance(string $class): object
|
||||
{
|
||||
if (in_array($class, $this->resolving, true)) {
|
||||
throw new CyclicDependencyException(
|
||||
dependencyChain: $this->resolving,
|
||||
class: $class
|
||||
);
|
||||
}
|
||||
|
||||
$this->resolving[] = $class;
|
||||
|
||||
try {
|
||||
$instance = $this->buildInstance($class);
|
||||
|
||||
if ($this->singletonDetector->isSingleton($class)) {
|
||||
$this->instances->setSingleton($class, $instance);
|
||||
} else {
|
||||
$this->instances->setInstance($class, $instance);
|
||||
}
|
||||
|
||||
return $instance;
|
||||
} finally {
|
||||
array_pop($this->resolving);
|
||||
}
|
||||
}
|
||||
|
||||
private function buildInstance(string $class): object
|
||||
{
|
||||
if ($this->bindings->hasBinding($class)) {
|
||||
return $this->resolveBinding($class, $this->bindings->getBinding($class));
|
||||
}
|
||||
|
||||
$reflection = $this->reflectionProvider->getClass($class);
|
||||
$dependencies = $this->dependencyResolver->resolveDependencies($class);
|
||||
|
||||
return $reflection->newInstanceArgs($dependencies);
|
||||
}
|
||||
|
||||
private function resolveBinding(string $class, callable|string|object $concrete): object
|
||||
{
|
||||
return match (true) {
|
||||
is_callable($concrete) => $concrete($this),
|
||||
is_string($concrete) => $this->get($concrete),
|
||||
default => $concrete
|
||||
};
|
||||
}
|
||||
|
||||
public function has(string $class): bool
|
||||
{
|
||||
return $this->instances->hasSingleton($class)
|
||||
|| $this->instances->hasInstance($class)
|
||||
|| $this->bindings->hasBinding($class)
|
||||
|| class_exists($class);
|
||||
}
|
||||
|
||||
public function forget(string $class): void
|
||||
{
|
||||
$this->instances->forget($class);
|
||||
$this->bindings->forget($class);
|
||||
$this->clearCaches($class);
|
||||
}
|
||||
|
||||
public function flush(): void
|
||||
{
|
||||
$this->instances->flush();
|
||||
$this->reflectionProvider->flush();
|
||||
$this->dependencyResolver->flushCache();
|
||||
$this->lazyInstantiator->flushFactories();
|
||||
|
||||
// Container selbst wieder registrieren
|
||||
$this->registerSelf();
|
||||
}
|
||||
|
||||
public function getRegisteredServices(): array
|
||||
{
|
||||
return array_merge(
|
||||
$this->instances->getAllRegistered(),
|
||||
$this->bindings->getAllBindings()
|
||||
);
|
||||
}
|
||||
|
||||
private function clearCaches(string $class): void
|
||||
{
|
||||
$this->reflectionProvider->forget($class);
|
||||
$this->dependencyResolver->clearCache($class);
|
||||
$this->lazyInstantiator->forgetFactory($class);
|
||||
}
|
||||
|
||||
private function registerSelf():void
|
||||
{
|
||||
$this->instances->setSingleton(self::class, $this);
|
||||
$this->instances->setSingleton(DefaultContainer::class, $this);
|
||||
$this->instances->setSingleton(Container::class, $this);
|
||||
}
|
||||
}
|
||||
110
src/Framework/DI/DependencyResolver.php
Normal file
110
src/Framework/DI/DependencyResolver.php
Normal file
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
namespace App\Framework\DI;
|
||||
|
||||
use App\Framework\DI\Exceptions\ParameterResolutionException;
|
||||
use App\Framework\Reflection\ReflectionProvider;
|
||||
use ReflectionNamedType;
|
||||
use ReflectionParameter;
|
||||
|
||||
/**
|
||||
* Löst Constructor-Abhängigkeiten auf
|
||||
*/
|
||||
final class DependencyResolver
|
||||
{
|
||||
private array $constructorCache = [];
|
||||
|
||||
public function __construct(
|
||||
private readonly ReflectionProvider $reflectionProvider,
|
||||
private readonly Container $container
|
||||
) {}
|
||||
|
||||
public function resolveDependencies(string $class): array
|
||||
{
|
||||
$reflection = $this->reflectionProvider->getClass($class);
|
||||
$constructor = $reflection->getConstructor();
|
||||
|
||||
if ($constructor === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (isset($this->constructorCache[$class])) {
|
||||
return $this->resolveFromCache($class);
|
||||
}
|
||||
|
||||
$dependencies = [];
|
||||
$cacheInfo = [];
|
||||
|
||||
foreach ($constructor->getParameters() as $param) {
|
||||
$dependency = $this->resolveParameter($param, $class);
|
||||
$dependencies[] = $dependency['value'];
|
||||
$cacheInfo[] = $dependency['cache'];
|
||||
}
|
||||
|
||||
// Nur cacheable Parameter cachen
|
||||
if (!in_array(false, array_column($cacheInfo, 'cacheable'), true)) {
|
||||
$this->constructorCache[$class] = $cacheInfo;
|
||||
}
|
||||
|
||||
return $dependencies;
|
||||
}
|
||||
|
||||
private function resolveParameter(ReflectionParameter $param, string $class): array
|
||||
{
|
||||
$type = $param->getType();
|
||||
|
||||
if ($param->isDefaultValueAvailable()) {
|
||||
return [
|
||||
'value' => $param->getDefaultValue(),
|
||||
'cache' => ['type' => 'default', 'value' => $param->getDefaultValue(), 'cacheable' => true]
|
||||
];
|
||||
}
|
||||
|
||||
if ($type instanceof ReflectionNamedType && $type->allowsNull()) {
|
||||
return [
|
||||
'value' => null,
|
||||
'cache' => ['type' => 'null', 'cacheable' => true]
|
||||
];
|
||||
}
|
||||
|
||||
if (!$type || $type->isBuiltin()) {
|
||||
throw new ParameterResolutionException(
|
||||
paramName: $param->getName(),
|
||||
class: $class,
|
||||
dependencyChain: [] // Wird vom Container gesetzt
|
||||
);
|
||||
}
|
||||
|
||||
$typeName = $type->getName();
|
||||
return [
|
||||
'value' => $this->container->get($typeName),
|
||||
'cache' => ['type' => 'dependency', 'class' => $typeName, 'cacheable' => false]
|
||||
];
|
||||
}
|
||||
|
||||
private function resolveFromCache(string $class): array
|
||||
{
|
||||
$cacheInfo = $this->constructorCache[$class];
|
||||
$dependencies = [];
|
||||
|
||||
foreach ($cacheInfo as $info) {
|
||||
$dependencies[] = match ($info['type']) {
|
||||
'default' => $info['value'],
|
||||
'null' => null,
|
||||
'dependency' => $this->container->get($info['class']),
|
||||
};
|
||||
}
|
||||
|
||||
return $dependencies;
|
||||
}
|
||||
|
||||
public function clearCache(string $class): void
|
||||
{
|
||||
unset($this->constructorCache[$class]);
|
||||
}
|
||||
|
||||
public function flushCache(): void
|
||||
{
|
||||
$this->constructorCache = [];
|
||||
}
|
||||
}
|
||||
27
src/Framework/DI/Exceptions/CyclicDependencyException.php
Normal file
27
src/Framework/DI/Exceptions/CyclicDependencyException.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\DI\Exceptions;
|
||||
|
||||
use App\Framework\Exception\FrameworkException;
|
||||
|
||||
final class CyclicDependencyException extends FrameworkException
|
||||
{
|
||||
public function __construct(
|
||||
array $dependencyChain,
|
||||
string $class,
|
||||
int $code = 0,
|
||||
?\Throwable $previous = null
|
||||
) {
|
||||
parent::__construct(
|
||||
message: 'Zyklische Abhängigkeit entdeckt: ' . implode(' -> ', $dependencyChain) . " -> $class",
|
||||
code: $code,
|
||||
previous: $previous,
|
||||
context: [
|
||||
'dependencyChain' => $dependencyChain,
|
||||
'class' => $class
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
22
src/Framework/DI/Exceptions/LazyLoadingException.php
Normal file
22
src/Framework/DI/Exceptions/LazyLoadingException.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\DI\Exceptions;
|
||||
|
||||
use App\Framework\Exception\FrameworkException;
|
||||
|
||||
final class LazyLoadingException extends FrameworkException
|
||||
{
|
||||
public function __construct(
|
||||
string $class,
|
||||
int $code = 0,
|
||||
?\Throwable $previous = null
|
||||
) {
|
||||
parent::__construct(
|
||||
message: "Lazy Loading wird für die Klasse $class nicht unterstützt",
|
||||
code: $code,
|
||||
previous: $previous
|
||||
);
|
||||
}
|
||||
}
|
||||
29
src/Framework/DI/Exceptions/ParameterResolutionException.php
Normal file
29
src/Framework/DI/Exceptions/ParameterResolutionException.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\DI\Exceptions;
|
||||
|
||||
use App\Framework\Exception\FrameworkException;
|
||||
|
||||
final class ParameterResolutionException extends FrameworkException
|
||||
{
|
||||
public function __construct(
|
||||
string $paramName,
|
||||
string $class,
|
||||
array $dependencyChain,
|
||||
int $code = 0,
|
||||
?\Throwable $previous = null
|
||||
) {
|
||||
parent::__construct(
|
||||
message: "Cannot resolve parameter {$paramName} in $class. Dependency chain: " . implode(' -> ', $dependencyChain),
|
||||
code: $code,
|
||||
previous: $previous,
|
||||
context: [
|
||||
'paramName' => $paramName,
|
||||
'class' => $class,
|
||||
'dependencyChain' => $dependencyChain
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
8
src/Framework/DI/Initializer.php
Normal file
8
src/Framework/DI/Initializer.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Framework\DI;
|
||||
|
||||
#[\Attribute(\Attribute::TARGET_METHOD)]
|
||||
final class Initializer
|
||||
{
|
||||
}
|
||||
23
src/Framework/DI/InitializerMapper.php
Normal file
23
src/Framework/DI/InitializerMapper.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Framework\DI;
|
||||
|
||||
use App\Framework\Core\AttributeMapper;
|
||||
|
||||
final readonly class InitializerMapper implements AttributeMapper
|
||||
{
|
||||
public function getAttributeClass(): string
|
||||
{
|
||||
return Initializer::class;
|
||||
}
|
||||
|
||||
public function map(object $reflectionTarget, object $attributeInstance): ?array
|
||||
{
|
||||
return [
|
||||
'class' => $reflectionTarget->getDeclaringClass()->getName(),
|
||||
'return' => $reflectionTarget->getReturnType()->getName(),
|
||||
'method' => $reflectionTarget->getName(),
|
||||
#'path' => $attributeInstance->path,
|
||||
];
|
||||
}
|
||||
}
|
||||
66
src/Framework/DI/InstanceRegistry.php
Normal file
66
src/Framework/DI/InstanceRegistry.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace App\Framework\DI;
|
||||
|
||||
/**
|
||||
* Verwaltet alle instanziierten Objekte und Singletons
|
||||
*/
|
||||
final class InstanceRegistry
|
||||
{
|
||||
private array $singletons = [];
|
||||
private array $instances = [];
|
||||
|
||||
public function getSingleton(string $class): ?object
|
||||
{
|
||||
return $this->singletons[$class] ?? null;
|
||||
}
|
||||
|
||||
public function hasSingleton(string $class): bool
|
||||
{
|
||||
return isset($this->singletons[$class]) && $this->singletons[$class] !== null;
|
||||
}
|
||||
|
||||
public function setSingleton(string $class, object $instance): void
|
||||
{
|
||||
$this->singletons[$class] = $instance;
|
||||
}
|
||||
|
||||
public function markAsSingleton(string $class): void
|
||||
{
|
||||
$this->singletons[$class] = null;
|
||||
}
|
||||
|
||||
public function getInstance(string $class): ?object
|
||||
{
|
||||
return $this->instances[$class] ?? null;
|
||||
}
|
||||
|
||||
public function hasInstance(string $class): bool
|
||||
{
|
||||
return isset($this->instances[$class]);
|
||||
}
|
||||
|
||||
public function setInstance(string $class, object $instance): void
|
||||
{
|
||||
$this->instances[$class] = $instance;
|
||||
}
|
||||
|
||||
public function forget(string $class): void
|
||||
{
|
||||
unset($this->instances[$class], $this->singletons[$class]);
|
||||
}
|
||||
|
||||
public function flush(): void
|
||||
{
|
||||
$this->instances = [];
|
||||
$this->singletons = [];
|
||||
}
|
||||
|
||||
public function getAllRegistered(): array
|
||||
{
|
||||
return array_merge(
|
||||
array_keys($this->singletons),
|
||||
array_keys($this->instances)
|
||||
);
|
||||
}
|
||||
}
|
||||
56
src/Framework/DI/LazyInstantiator.php
Normal file
56
src/Framework/DI/LazyInstantiator.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace App\Framework\DI;
|
||||
|
||||
use App\Framework\DI\Exceptions\LazyLoadingException;
|
||||
use App\Framework\Reflection\ReflectionProvider;
|
||||
use Closure;
|
||||
|
||||
/**
|
||||
* Behandelt Lazy Loading von Objekten
|
||||
*/
|
||||
final class LazyInstantiator
|
||||
{
|
||||
private array $factories = [];
|
||||
|
||||
public function __construct(
|
||||
private readonly ReflectionProvider $reflectionProvider,
|
||||
private readonly Closure $instanceCreator
|
||||
) {}
|
||||
|
||||
public function canUseLazyLoading(string $class, InstanceRegistry $registry): bool
|
||||
{
|
||||
return !$registry->hasSingleton($class);
|
||||
}
|
||||
|
||||
public function createLazyInstance(string $class): object
|
||||
{
|
||||
$reflection = $this->reflectionProvider->getClass($class);
|
||||
|
||||
if (!isset($this->factories[$class])) {
|
||||
$this->factories[$class] = fn() => ($this->instanceCreator)($class);
|
||||
}
|
||||
|
||||
$factory = $this->factories[$class];
|
||||
|
||||
if (method_exists($reflection, 'getLazyGhost')) {
|
||||
return $reflection->getLazyGhost($factory);
|
||||
}
|
||||
|
||||
if (method_exists($reflection, 'getLazyProxy')) {
|
||||
return $reflection->getLazyProxy($factory);
|
||||
}
|
||||
|
||||
throw new LazyLoadingException("Lazy loading not supported for class: {$class}");
|
||||
}
|
||||
|
||||
public function forgetFactory(string $class): void
|
||||
{
|
||||
unset($this->factories[$class]);
|
||||
}
|
||||
|
||||
public function flushFactories(): void
|
||||
{
|
||||
$this->factories = [];
|
||||
}
|
||||
}
|
||||
127
src/Framework/DI/MethodInvoker.php
Normal file
127
src/Framework/DI/MethodInvoker.php
Normal file
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
namespace App\Framework\DI;
|
||||
|
||||
use App\Framework\Reflection\ReflectionProvider;
|
||||
use ReflectionMethod;
|
||||
use ReflectionNamedType;
|
||||
|
||||
/**
|
||||
* Führt Methoden auf beliebigen Klassen mit automatischer Dependency Injection aus
|
||||
*/
|
||||
final readonly class MethodInvoker
|
||||
{
|
||||
public function __construct(
|
||||
private Container $container,
|
||||
private ReflectionProvider $reflectionCache
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Führt eine Methode auf einer Klasse aus und löst alle Parameter automatisch auf
|
||||
*/
|
||||
public function invoke(string $className, string $methodName, array $overrides = []): mixed
|
||||
{
|
||||
$instance = $this->container->get($className);
|
||||
return $this->invokeOn($instance, $methodName, $overrides);
|
||||
}
|
||||
|
||||
/**
|
||||
* Führt eine Methode auf einer bereits existierenden Instanz aus
|
||||
*/
|
||||
public function invokeOn(object $instance, string $methodName, array $overrides = []): mixed
|
||||
{
|
||||
$reflection = $this->reflectionCache->getClass($instance::class);
|
||||
|
||||
if (!$reflection->hasMethod($methodName)) {
|
||||
throw new \InvalidArgumentException(
|
||||
"Method $methodName does not exist on class $instance::class"
|
||||
);
|
||||
}
|
||||
|
||||
$method = $this->reflectionCache->getMethod($instance::class, $methodName);
|
||||
|
||||
if (!$method->isPublic()) {
|
||||
throw new \InvalidArgumentException(
|
||||
"Method $methodName on class $instance::class is not public"
|
||||
);
|
||||
}
|
||||
|
||||
$parameters = $this->resolveMethodParameters($method, $overrides);
|
||||
|
||||
return $method->invokeArgs($instance, $parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Führt eine statische Methode aus
|
||||
*/
|
||||
public function invokeStatic(string $className, string $methodName, array $overrides = []): mixed
|
||||
{
|
||||
$reflection = $this->reflectionCache->getClass($className);
|
||||
|
||||
if (!$reflection->hasMethod($methodName)) {
|
||||
throw new \InvalidArgumentException(
|
||||
"Method '{$methodName}' does not exist on class '{$className}'"
|
||||
);
|
||||
}
|
||||
|
||||
$method = $this->reflectionCache->getMethod($className, $methodName);
|
||||
|
||||
if (!$method->isStatic()) {
|
||||
throw new \InvalidArgumentException(
|
||||
"Method '{$methodName}' on class '{$className}' is not static"
|
||||
);
|
||||
}
|
||||
|
||||
$parameters = $this->resolveMethodParameters($method, $overrides);
|
||||
|
||||
return $method->invokeArgs(null, $parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Löst alle Parameter einer Methode auf
|
||||
*/
|
||||
private function resolveMethodParameters(ReflectionMethod $method, array $overrides): array
|
||||
{
|
||||
$parameters = [];
|
||||
|
||||
foreach ($method->getParameters() as $param) {
|
||||
$paramName = $param->getName();
|
||||
|
||||
// Override-Werte haben Priorität
|
||||
if (array_key_exists($paramName, $overrides)) {
|
||||
$parameters[] = $overrides[$paramName];
|
||||
continue;
|
||||
}
|
||||
|
||||
// Type-based injection
|
||||
$type = $param->getType();
|
||||
|
||||
if ($type instanceof ReflectionNamedType && !$type->isBuiltin()) {
|
||||
$typeName = $type->getName();
|
||||
|
||||
if ($this->container->has($typeName)) {
|
||||
$parameters[] = $this->container->get($typeName);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Default-Werte verwenden
|
||||
if ($param->isDefaultValueAvailable()) {
|
||||
$parameters[] = $param->getDefaultValue();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Nullable Parameter
|
||||
if ($type && $type->allowsNull()) {
|
||||
$parameters[] = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException(
|
||||
"Cannot resolve parameter '{$paramName}' for method '{$method->getName()}' on class '{$method->getDeclaringClass()->getName()}'"
|
||||
);
|
||||
}
|
||||
|
||||
return $parameters;
|
||||
}
|
||||
}
|
||||
64
src/Framework/DI/ReflectionCache.php
Normal file
64
src/Framework/DI/ReflectionCache.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace App\Framework\DI;
|
||||
|
||||
use ReflectionClass;
|
||||
use ReflectionMethod;
|
||||
|
||||
/**
|
||||
* Cache für ReflectionClass-Instanzen
|
||||
*/
|
||||
final class ReflectionCache
|
||||
{
|
||||
private array $classCache = [];
|
||||
private array $methodCache = [];
|
||||
|
||||
private array $parameterCache = [];
|
||||
|
||||
public function get(string $class): ReflectionClass
|
||||
{
|
||||
return $this->classCache[$class] ??= new ReflectionClass($class);
|
||||
}
|
||||
|
||||
public function getMethod(string $class, string $method): ReflectionMethod
|
||||
{
|
||||
$key = "{$class}::{$method}";
|
||||
return $this->methodCache[$key] ??= $this->get($class)->getMethod($method);
|
||||
}
|
||||
|
||||
public function getMethodParameters(string $class, string $method): array
|
||||
{
|
||||
$key = "{$class}::{$method}::params";
|
||||
return $this->parameterCache[$key] ??= $this->getMethod($class, $method)->getParameters();
|
||||
}
|
||||
|
||||
public function forget(string $class): void
|
||||
{
|
||||
unset($this->classCache[$class]);
|
||||
|
||||
// Alle Methods dieser Klasse aus dem Cache entfernen
|
||||
$this->methodCache = array_filter(
|
||||
$this->methodCache,
|
||||
fn($key) => !str_starts_with($key, $class . '::'),
|
||||
ARRAY_FILTER_USE_KEY
|
||||
);
|
||||
|
||||
$this->parameterCache = array_filter(
|
||||
$this->parameterCache,
|
||||
fn($key) => !str_starts_with($key, $class . '::'),
|
||||
ARRAY_FILTER_USE_KEY
|
||||
);
|
||||
}
|
||||
|
||||
public function forgetMethod(string $class, string $method): void
|
||||
{
|
||||
unset($this->methodCache["{$class}::{$method}"]);
|
||||
}
|
||||
|
||||
public function flush(): void
|
||||
{
|
||||
$this->classCache = [];
|
||||
$this->methodCache = [];
|
||||
$this->parameterCache = [];
|
||||
}
|
||||
}
|
||||
27
src/Framework/DI/SingletonDetector.php
Normal file
27
src/Framework/DI/SingletonDetector.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Framework\DI;
|
||||
|
||||
use App\Framework\Attributes\Singleton;
|
||||
use App\Framework\Reflection\ReflectionProvider;
|
||||
|
||||
/**
|
||||
* Erkennt ob eine Klasse als Singleton markiert ist
|
||||
*/
|
||||
final readonly class SingletonDetector
|
||||
{
|
||||
public function __construct(
|
||||
private ReflectionProvider $reflectionProvider,
|
||||
private InstanceRegistry $instanceRegistry
|
||||
) {}
|
||||
|
||||
public function isSingleton(string $class): bool
|
||||
{
|
||||
if ($this->instanceRegistry->hasSingleton($class)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$reflection = $this->reflectionProvider->getClass($class);
|
||||
return count($reflection->getAttributes(Singleton::class)) > 0;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user