chore: complete update

This commit is contained in:
2025-07-17 16:24:20 +02:00
parent 899227b0a4
commit 64a7051137
1300 changed files with 85570 additions and 2756 deletions

View 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;
}
}

View 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);
}
}

View File

@@ -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;
}

View 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
}
}
}

View 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

View 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);
}
}

View 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 = [];
}
}

View 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
]
);
}
}

View 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
);
}
}

View 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
]
);
}
}

View File

@@ -0,0 +1,8 @@
<?php
namespace App\Framework\DI;
#[\Attribute(\Attribute::TARGET_METHOD)]
final class Initializer
{
}

View 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,
];
}
}

View 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)
);
}
}

View 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 = [];
}
}

View 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;
}
}

View 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 = [];
}
}

View 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;
}
}