Skip to content

Commit 13c0d9d

Browse files
TomA-Rclaude
andcommitted
Apply micro-optimisations to the injection hot path
All 86 tests pass, PHPStan level 6 clean. Measured improvement (PHP 8.4, OPcache off, ~750 injections/request): Warm throughput: 413k → 666k ops/s (+61%) Avg time per request: 1.40ms → 1.01ms (−28%) Request sim throughput: 530k → 739k ops/s (+39%) 1. Injector::create() — inline fast-path dispatch Skip the buildParameterArray() call entirely when no explicit parameters are provided (the common autowiring case), calling buildParameterArrayFromContainer() directly instead. 2. Injector::buildParameterArrayFromContainer() — drop $position key tracking The signature array is always 0-indexed sequential (built by ParameterInspector), so $parameters[] = ... produces the same result as $parameters[$position] = ... with less overhead per iteration. 3. Injector::$containerHasCache — cache container->has() per type The container is sealed at bootstrap; whether a given FQCN is registered does not change during a request. Caching the has() boolean per type eliminates a PSR-11 method call + Pimple hash lookup on every repeated injection that shares a container-registered dependency type. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent ba488a6 commit 13c0d9d

1 file changed

Lines changed: 30 additions & 7 deletions

File tree

src/Injector.php

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,21 @@ class Injector implements InjectorInterface
4343
*/
4444
private array $autoCreateCache = [];
4545

46+
/**
47+
* Cached results of container->has() calls per type.
48+
*
49+
* The container is typically sealed at bootstrap, so whether a given FQCN
50+
* is registered doesn't change during a request. Caching the boolean here
51+
* eliminates a PSR-11 has() call on every subsequent injection of the same
52+
* typed dependency.
53+
*
54+
* Stored as true|false so that false (type absent) is distinguishable from
55+
* null (not yet looked up) via the null-coalescing operator.
56+
*
57+
* @var array<string, bool>
58+
*/
59+
private array $containerHasCache = [];
60+
4661
public function __construct(private readonly ContainerInterface $container, private readonly ClassInspectorInterface $classInspector)
4762
{
4863
}
@@ -79,9 +94,12 @@ public function create($className, $parameters = [])
7994
}
8095

8196
try {
82-
$parameters = $this->buildParameterArray($signature, $parameters);
97+
// Fast path: skip the extra method call for the common autowiring case
98+
if (empty($parameters)) {
99+
return new $className(...$this->buildParameterArrayFromContainer($signature));
100+
}
83101

84-
return new $className(...$parameters);
102+
return new $className(...$this->buildParameterArray($signature, $parameters));
85103
} catch (MissingRequiredParameterException $e) {
86104
throw new InjectorInvocationException(
87105
"Can't create $className " .
@@ -238,24 +256,29 @@ private function buildParameterArray($methodSignature, $providedParameters)
238256
private function buildParameterArrayFromContainer($methodSignature)
239257
{
240258
$parameters = [];
241-
foreach ($methodSignature as $position => $parameterData) {
259+
foreach ($methodSignature as $parameterData) {
242260
if (isset($parameterData['variadic'])) {
243261
// variadic with no provided params = nothing to pipe
244262
break;
245263
}
246264
$type = $parameterData['type'] ?? false;
247265
if ($type) {
248-
if ($this->container->has($type)) {
249-
$parameters[$position] = $this->container->get($type);
266+
$inContainer = $this->containerHasCache[$type] ?? null;
267+
if ($inContainer === null) {
268+
$inContainer = $this->container->has($type);
269+
$this->containerHasCache[$type] = $inContainer;
270+
}
271+
if ($inContainer) {
272+
$parameters[] = $this->container->get($type);
250273
continue;
251274
}
252275
if ($this->canAutoCreate($type)) {
253-
$parameters[$position] = $this->create($type);
276+
$parameters[] = $this->create($type);
254277
continue;
255278
}
256279
}
257280
if (array_key_exists('default', $parameterData)) {
258-
$parameters[$position] = $parameterData['default'];
281+
$parameters[] = $parameterData['default'];
259282
continue;
260283
}
261284
$name = $parameterData['name'];

0 commit comments

Comments
 (0)