From 0dadce09abda10d26ce46e19b513080112147cef Mon Sep 17 00:00:00 2001 From: Mark Date: Fri, 5 Jun 2026 13:03:11 +0200 Subject: [PATCH 1/2] perf(container): cache dynamic initializer instances --- packages/container/src/GenericContainer.php | 15 +++++++- packages/container/tests/ContainerTest.php | 14 +++++++ .../Fixtures/CountingDynamicInitializer.php | 37 +++++++++++++++++++ 3 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 packages/container/tests/Fixtures/CountingDynamicInitializer.php diff --git a/packages/container/src/GenericContainer.php b/packages/container/src/GenericContainer.php index 02d8bdae8..1d4ee4f3f 100644 --- a/packages/container/src/GenericContainer.php +++ b/packages/container/src/GenericContainer.php @@ -28,6 +28,9 @@ final class GenericContainer implements Container { use HasInstance; + /** @var array, DynamicInitializer> */ + private array $resolvedDynamicInitializers = []; + public function __construct( /** @var ArrayIterator $definitions */ private(set) ArrayIterator $definitions = new ArrayIterator(), @@ -81,6 +84,7 @@ public function setInitializers(array $initializers): self public function setDynamicInitializers(array $dynamicInitializers): self { $this->dynamicInitializers = new ArrayIterator($dynamicInitializers); + $this->resolvedDynamicInitializers = []; return $this; } @@ -330,6 +334,7 @@ public function removeInitializer(ClassReflector|string $initializerClass): Cont ); unset($this->dynamicInitializers[$index]); + unset($this->resolvedDynamicInitializers[$initializerClass->getName()]); return $this; } @@ -444,8 +449,13 @@ private function initializerForClass(ClassReflector $target, null|string|UnitEnu // Loop through the registered initializers to see if // we have something to handle this class. foreach ($this->dynamicInitializers as $initializerClass) { - /** @var DynamicInitializer $initializer */ - $initializer = $this->resolve($initializerClass); + if (! isset($this->resolvedDynamicInitializers[$initializerClass])) { + /** @var DynamicInitializer $initializer */ + $initializer = $this->resolve($initializerClass); + $this->resolvedDynamicInitializers[$initializerClass] = $initializer; + } + + $initializer = $this->resolvedDynamicInitializers[$initializerClass]; if (! $initializer->canInitialize(class: $target, tag: $tag)) { continue; @@ -719,6 +729,7 @@ public function addResettable(string|ClassReflector $resettableClass): Container public function reset(): self { $this->resolvedSingletons = new ArrayIterator(); + $this->resolvedDynamicInitializers = []; foreach ($this->resettables as $resettableClass) { /** @var Resettable $resettable */ diff --git a/packages/container/tests/ContainerTest.php b/packages/container/tests/ContainerTest.php index a3e22824b..9a66cc8c2 100644 --- a/packages/container/tests/ContainerTest.php +++ b/packages/container/tests/ContainerTest.php @@ -34,6 +34,7 @@ use Tempest\Container\Tests\Fixtures\ContainerObjectDInitializer; use Tempest\Container\Tests\Fixtures\ContainerObjectE; use Tempest\Container\Tests\Fixtures\ContainerObjectEInitializer; +use Tempest\Container\Tests\Fixtures\CountingDynamicInitializer; use Tempest\Container\Tests\Fixtures\DecoratedClass; use Tempest\Container\Tests\Fixtures\DecoratedInterface; use Tempest\Container\Tests\Fixtures\DecoratorClass; @@ -157,6 +158,19 @@ public function test_call_tries_to_transform_unmatched_values(): void $this->assertSame('other', $return->id); } + public function test_dynamic_initializer_instances_are_reused(): void + { + CountingDynamicInitializer::reset(); + + $container = new GenericContainer(); + $container->addInitializer(CountingDynamicInitializer::class); + + $container->get(ContainerObjectA::class); + $container->get(ContainerObjectA::class); + + $this->assertSame(1, CountingDynamicInitializer::$instances); + } + public function test_arrays_are_automatically_created(): void { $container = new GenericContainer(); diff --git a/packages/container/tests/Fixtures/CountingDynamicInitializer.php b/packages/container/tests/Fixtures/CountingDynamicInitializer.php new file mode 100644 index 000000000..938683ff0 --- /dev/null +++ b/packages/container/tests/Fixtures/CountingDynamicInitializer.php @@ -0,0 +1,37 @@ +counted) { + $this->counted = true; + self::$instances++; + } + + return false; + } + + public function initialize(ClassReflector $class, null|string|UnitEnum $tag, Container $container): object + { + throw new \LogicException('CountingDynamicInitializer should not initialize objects.'); + } +} From 718e7116c24eac427d7d30705bd07555be9e9944 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rk=20Magyar?= Date: Fri, 5 Jun 2026 13:18:13 +0200 Subject: [PATCH 2/2] chore(container): inline resolve call Co-authored-by: Enzo Innocenzi --- packages/container/src/GenericContainer.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/container/src/GenericContainer.php b/packages/container/src/GenericContainer.php index 1d4ee4f3f..12e2538cc 100644 --- a/packages/container/src/GenericContainer.php +++ b/packages/container/src/GenericContainer.php @@ -450,9 +450,7 @@ private function initializerForClass(ClassReflector $target, null|string|UnitEnu // we have something to handle this class. foreach ($this->dynamicInitializers as $initializerClass) { if (! isset($this->resolvedDynamicInitializers[$initializerClass])) { - /** @var DynamicInitializer $initializer */ - $initializer = $this->resolve($initializerClass); - $this->resolvedDynamicInitializers[$initializerClass] = $initializer; + $this->resolvedDynamicInitializers[$initializerClass] = $this->resolve($initializerClass); } $initializer = $this->resolvedDynamicInitializers[$initializerClass];