Files
michaelschiemer/src/Framework/DI/DefaultContainer.php
Michael Schiemer 5050c7d73a docs: consolidate documentation into organized structure
- Move 12 markdown files from root to docs/ subdirectories
- Organize documentation by category:
  • docs/troubleshooting/ (1 file)  - Technical troubleshooting guides
  • docs/deployment/      (4 files) - Deployment and security documentation
  • docs/guides/          (3 files) - Feature-specific guides
  • docs/planning/        (4 files) - Planning and improvement proposals

Root directory cleanup:
- Reduced from 16 to 4 markdown files in root
- Only essential project files remain:
  • CLAUDE.md (AI instructions)
  • README.md (Main project readme)
  • CLEANUP_PLAN.md (Current cleanup plan)
  • SRC_STRUCTURE_IMPROVEMENTS.md (Structure improvements)

This improves:
 Documentation discoverability
 Logical organization by purpose
 Clean root directory
 Better maintainability
2025-10-05 11:05:04 +02:00

276 lines
9.4 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);
$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);
if ($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
{
$className = ClassName::create($class);
if ($this->bindings->hasBinding($class)) {
return $this->resolveBinding($class, $this->bindings->getBinding($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);
}
}