- Add comprehensive health check system with multiple endpoints - Add Prometheus metrics endpoint - Add production logging configurations (5 strategies) - Add complete deployment documentation suite: * QUICKSTART.md - 30-minute deployment guide * DEPLOYMENT_CHECKLIST.md - Printable verification checklist * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference * production-logging.md - Logging configuration guide * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation * README.md - Navigation hub * DEPLOYMENT_SUMMARY.md - Executive summary - Add deployment scripts and automation - Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment - Update README with production-ready features All production infrastructure is now complete and ready for deployment.
294 lines
10 KiB
PHP
294 lines
10 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\DI;
|
|
|
|
use App\Framework\Core\ValueObjects\ClassName;
|
|
use App\Framework\DI\Exceptions\CyclicDependencyException;
|
|
use App\Framework\DI\Exceptions\LazyLoadingException;
|
|
use App\Framework\Metrics\FrameworkMetricsCollector;
|
|
use App\Framework\Reflection\CachedReflectionProvider;
|
|
use App\Framework\Reflection\ReflectionProvider;
|
|
use Throwable;
|
|
|
|
final class DefaultContainer implements Container
|
|
{
|
|
/** @var class-string[] */
|
|
private array $resolving = [];
|
|
|
|
private readonly DependencyResolver $dependencyResolver;
|
|
|
|
private readonly SingletonDetector $singletonDetector;
|
|
|
|
private readonly LazyInstantiator $lazyInstantiator;
|
|
|
|
public readonly MethodInvoker $invoker;
|
|
|
|
public readonly ContainerIntrospector $introspector;
|
|
|
|
public readonly FrameworkMetricsCollector $metrics;
|
|
|
|
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->introspector = new ContainerIntrospector(
|
|
$this,
|
|
$this->instances,
|
|
$this->bindings,
|
|
$this->reflectionProvider,
|
|
fn (): array => $this->resolving
|
|
);
|
|
$this->metrics = new FrameworkMetricsCollector();
|
|
|
|
$this->registerSelf();
|
|
$this->instance(ReflectionProvider::class, $this->reflectionProvider);
|
|
}
|
|
|
|
public function bind(string $abstract, callable|string|object $concrete): void
|
|
{
|
|
$this->bindings->bind($abstract, $concrete);
|
|
|
|
// Only clear caches for valid class names, skip string keys like 'filesystem.storage.local'
|
|
if (class_exists($abstract) || interface_exists($abstract)) {
|
|
$this->clearCaches(ClassName::create($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);
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function get(string|ClassName $class): object
|
|
{
|
|
$className = $class instanceof ClassName ? $class->toString() : $class;
|
|
|
|
// Bereits instanziierte Objekte zurückgeben
|
|
if ($this->instances->hasSingleton($className)) {
|
|
$singleton = $this->instances->getSingleton($className);
|
|
if ($singleton !== null) {
|
|
return $singleton;
|
|
}
|
|
}
|
|
|
|
if ($this->instances->hasInstance($className)) {
|
|
return $this->instances->getInstance($className);
|
|
}
|
|
|
|
// Lazy Loading versuchen
|
|
if ($this->lazyInstantiator->canUseLazyLoading($className, $this->instances)) {
|
|
try {
|
|
return $this->lazyInstantiator->createLazyInstance($className);
|
|
} catch (Throwable $e) {
|
|
if (! $e instanceof LazyLoadingException) {
|
|
throw $e;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $this->createInstance($className);
|
|
}
|
|
|
|
/**
|
|
* @template T of object
|
|
* @param class-string<T> $class
|
|
* @return T
|
|
*/
|
|
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);
|
|
|
|
// Only check singleton attribute for actual classes
|
|
if ((class_exists($class) || interface_exists($class)) &&
|
|
$this->singletonDetector->isSingleton(ClassName::create($class))) {
|
|
$this->instances->setSingleton($class, $instance);
|
|
} else {
|
|
$this->instances->setInstance($class, $instance);
|
|
}
|
|
|
|
return $instance;
|
|
} catch (Throwable $e) {
|
|
// Track resolution failures
|
|
$this->metrics->increment('container.resolve.failures');
|
|
|
|
throw $e;
|
|
} finally {
|
|
array_pop($this->resolving);
|
|
}
|
|
}
|
|
|
|
private function buildInstance(string $class): object
|
|
{
|
|
if ($this->bindings->hasBinding($class)) {
|
|
return $this->resolveBinding($class, $this->bindings->getBinding($class));
|
|
}
|
|
|
|
// For string keys without bindings, throw immediately
|
|
if (!class_exists($class) && !interface_exists($class)) {
|
|
throw new \RuntimeException(
|
|
"Cannot resolve '{$class}': not a valid class and no binding exists. " .
|
|
"Available bindings: " . implode(', ', array_keys($this->bindings->getAllBindings()))
|
|
);
|
|
}
|
|
|
|
$className = ClassName::create($class);
|
|
|
|
// Enhanced diagnostics for missing bindings
|
|
try {
|
|
$reflection = $this->reflectionProvider->getClass($className);
|
|
|
|
// Check if class is instantiable using framework's method
|
|
if (! $reflection->isInstantiable()) {
|
|
$this->throwDetailedBindingException($class, $reflection);
|
|
}
|
|
|
|
$dependencies = $this->dependencyResolver->resolveDependencies($className);
|
|
|
|
return $reflection->newInstance(...$dependencies->toArray());
|
|
} catch (\RuntimeException $e) {
|
|
// If it's already our detailed exception, just re-throw
|
|
if (str_contains($e->getMessage(), 'Dependency resolution chain:')) {
|
|
throw $e;
|
|
}
|
|
|
|
// Otherwise, wrap with binding information
|
|
throw new \RuntimeException(
|
|
"Cannot resolve class '{$class}': {$e->getMessage()}. " .
|
|
"Available bindings: " . implode(', ', array_keys($this->bindings->getAllBindings())) .
|
|
". Dependency chain: " . implode(' -> ', $this->resolving),
|
|
0,
|
|
$e
|
|
);
|
|
} catch (\ReflectionException $e) {
|
|
throw new \RuntimeException(
|
|
"Cannot resolve class '{$class}': {$e->getMessage()}. " .
|
|
"Available bindings: " . implode(', ', array_keys($this->bindings->getAllBindings())) .
|
|
". Dependency chain: " . implode(' -> ', $this->resolving),
|
|
0,
|
|
$e
|
|
);
|
|
}
|
|
}
|
|
|
|
private function throwDetailedBindingException(string $class, $reflection): never
|
|
{
|
|
$availableBindings = array_keys($this->bindings->getAllBindings());
|
|
$dependencyChain = implode(' -> ', $this->resolving);
|
|
|
|
// Look for similar interface bindings
|
|
$similarBindings = array_filter($availableBindings, function ($binding) use ($class) {
|
|
return str_contains($binding, basename(str_replace('\\', '/', $class)));
|
|
});
|
|
|
|
$message = "Cannot instantiate class '{$class}': class is not instantiable (interface, abstract class, or trait).\n" .
|
|
"Dependency resolution chain: {$dependencyChain}\n" .
|
|
"Total available bindings: " . count($availableBindings) . "\n";
|
|
|
|
if (! empty($similarBindings)) {
|
|
$message .= "Similar bindings found: " . implode(', ', $similarBindings) . "\n";
|
|
}
|
|
|
|
$message .= "All bindings: " . implode(', ', $availableBindings);
|
|
|
|
throw new \RuntimeException($message);
|
|
}
|
|
|
|
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
|
|
};
|
|
}
|
|
|
|
/** @param class-string $class */
|
|
public function has(string $class): bool
|
|
{
|
|
return $this->instances->hasSingleton($class)
|
|
|| $this->instances->hasInstance($class)
|
|
|| $this->bindings->hasBinding($class)
|
|
|| (! empty($class) && ClassName::create($class)->exists() && $this->reflectionProvider->getClass(ClassName::create($class))->isInstantiable());
|
|
}
|
|
|
|
/** @param class-string $class */
|
|
public function forget(string $class): void
|
|
{
|
|
$this->instances->forget($class);
|
|
$this->bindings->forget($class);
|
|
$this->clearCaches(ClassName::create($class));
|
|
}
|
|
|
|
public function flush(): void
|
|
{
|
|
$this->instances->flush();
|
|
$this->bindings->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()
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get all registered service IDs (for debugging/admin)
|
|
* @return array<string>
|
|
*/
|
|
public function getServiceIds(): array
|
|
{
|
|
return array_keys($this->getRegisteredServices());
|
|
}
|
|
|
|
private function clearCaches(ClassName $className): void
|
|
{
|
|
$this->reflectionProvider->forget($className);
|
|
$this->dependencyResolver->clearCache($className);
|
|
$this->lazyInstantiator->forgetFactory($className->getFullyQualified());
|
|
}
|
|
|
|
private function registerSelf(): void
|
|
{
|
|
$this->instances->setSingleton(self::class, $this);
|
|
$this->instances->setSingleton(DefaultContainer::class, $this);
|
|
$this->instances->setSingleton(Container::class, $this);
|
|
}
|
|
}
|