Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 151 additions & 14 deletions src/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,27 @@ class Container implements ContainerContract
*/
protected array $resolved = [];

/**
* Reflection cache to avoid repeated ReflectionClass instantiation.
*
* @var array<string, ReflectionClass>
*/
protected array $reflectionCache = [];

/**
* Flattened alias cache to avoid recursive lookups.
*
* @var array<string, string>
*/
protected array $flattenedAliases = [];

/**
* Dependency cache for constructor parameters.
*
* @var array<string, array>
*/
protected array $dependencyCache = [];

/**
* Set current container instance.
*
Expand Down Expand Up @@ -81,6 +102,10 @@ public function bind(string $abstract, string|callable $concrete = null, bool $s
{
if (is_null($concrete)) $concrete = $abstract;
$this->bindings[$abstract] = compact('concrete', 'shared');

// Clear caches that might be affected
unset($this->dependencyCache[$abstract]);
unset($this->reflectionCache[$abstract]);
}

/**
Expand All @@ -105,6 +130,8 @@ public function singleton(string $abstract, string|callable $concrete = null): v
public function alias(string $abstract, string $alias): void
{
$this->aliases[$alias] = $abstract;
// Clear flattened alias cache when new aliases are added
$this->flattenedAliases = [];
}

/**
Expand All @@ -120,28 +147,61 @@ public function alias(string $abstract, string $alias): void
*/
public function make(string $abstract, array $params = []): mixed
{
$original = $abstract;
$abstract = $this->getAlias($abstract);
if (isset($this->instances[$abstract])) return $this->instances[$abstract];

// Fast path for singletons
if (isset($this->instances[$abstract])) {
return $this->instances[$abstract];
}

$concrete = $this->getConcrete($abstract);
if ($this->isBuildable($concrete, $abstract)) $obj = $this->build($concrete, $params);
else $obj = $this->make($concrete);

if ($this->isBuildable($concrete, $abstract)) {
$obj = $this->build($concrete, $params);
} else {
$obj = $this->make($concrete);
}

if ($this->isShared($abstract)) $this->instances[$abstract] = $obj;
if ($this->isShared($abstract)) {
$this->instances[$abstract] = $obj;
}

$this->resolved[$abstract] = true;

return $obj;
}

/**
* Get abstract alias.
* Get abstract alias with flattened cache for performance.
*
* @param string $abstract
* @return string
*/
protected function getAlias(string $abstract): string
{
return isset($this->aliases[$abstract]) ? $this->getAlias($this->aliases[$abstract]) : $abstract;
// Use cached flattened alias if available
if (isset($this->flattenedAliases[$abstract])) {
return $this->flattenedAliases[$abstract];
}

$original = $abstract;
$resolved = $abstract;
$seen = [];

// Flatten alias chain and cache result
while (isset($this->aliases[$resolved])) {
if (isset($seen[$resolved])) {
// Circular reference detected
break;
}
$seen[$resolved] = true;
$resolved = $this->aliases[$resolved];
}

// Cache the flattened result
$this->flattenedAliases[$original] = $resolved;
return $resolved;
}

/**
Expand Down Expand Up @@ -184,7 +244,7 @@ protected function isShared(string $abstract): bool
}

/**
* Build concrete.
* Build concrete with cached reflection.
*
* @param callable|string $concrete
* @param array $params
Expand All @@ -200,22 +260,70 @@ protected function build(callable|string $concrete, array $params = []): mixed
return $concrete($this, ...array_values($params));
}

try {
$reflector = new ReflectionClass($concrete);
} catch (ReflectionException) {
throw new NotFoundException("Unable to resolve class [$concrete]");
}
$reflector = $this->getReflection($concrete);

if (!$reflector->isInstantiable()) {
throw new BindingResolutionException("Target [$concrete] is not instantiable.");
}

$constructor = $reflector->getConstructor();
if (is_null($constructor)) return $reflector->newInstance();
if (is_null($constructor)) {
return $reflector->newInstance();
}

$dependencies = $this->getCachedDependencies($concrete, $constructor, $params);
return $reflector->newInstanceArgs($dependencies);
}

/**
* Get cached reflection class to avoid repeated instantiation.
*
* @param string $concrete
* @return ReflectionClass
* @throws NotFoundException
*/
protected function getReflection(string $concrete): ReflectionClass
{
if (!isset($this->reflectionCache[$concrete])) {
try {
$this->reflectionCache[$concrete] = new ReflectionClass($concrete);
} catch (ReflectionException) {
throw new NotFoundException("Unable to resolve class [$concrete]");
}
}

return $this->reflectionCache[$concrete];
}

/**
* Get dependencies with caching for better performance.
*
* @param string $concrete
* @param ReflectionMethod $constructor
* @param array $params
* @return array
* @throws NotFoundException
* @throws ReflectionException
* @throws BindingResolutionException
*/
protected function getCachedDependencies(string $concrete, ReflectionMethod $constructor, array $params = []): array
{
$cacheKey = $concrete;

// If we have cached dependencies and no custom params, use cache
if (empty($params) && isset($this->dependencyCache[$cacheKey])) {
return $this->dependencyCache[$cacheKey];
}

$dependencies = $constructor->getParameters();
$args = $this->getDependencies($dependencies, $params);
return $reflector->newInstanceArgs($args);

// Cache only if no custom params were provided
if (empty($params)) {
$this->dependencyCache[$cacheKey] = $args;
}

return $args;
}

/**
Expand Down Expand Up @@ -323,4 +431,33 @@ public function has(string $id): bool
{
return $this->bound($id);
}

/**
* Clear all caches for memory management.
*
* @return void
*/
public function clearCaches(): void
{
$this->reflectionCache = [];
$this->flattenedAliases = [];
$this->dependencyCache = [];
}

/**
* Get cache statistics for debugging.
*
* @return array
*/
public function getCacheStats(): array
{
return [
'reflection_cache_size' => count($this->reflectionCache),
'flattened_aliases_size' => count($this->flattenedAliases),
'dependency_cache_size' => count($this->dependencyCache),
'bindings_count' => count($this->bindings),
'instances_count' => count($this->instances),
'aliases_count' => count($this->aliases),
];
}
}