From 6eed91c61f6fe8ec17fc7f676543b14c1e0b40ca Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sat, 23 Aug 2025 22:06:57 +0200 Subject: [PATCH 1/2] hold services inside a Map --- src/Container.php | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/Container.php b/src/Container.php index 56805ff..3e78f8c 100644 --- a/src/Container.php +++ b/src/Container.php @@ -15,18 +15,17 @@ final class Container { - /** @var array */ - private array $services = []; - /** * @psalm-mutation-free * * @param Map $definitions * @param Sequence $building + * @param Map $services */ private function __construct( private Map $definitions, private Sequence $building, + private Map $services, ) { } @@ -75,7 +74,18 @@ public function __invoke(Service $name): object * @psalm-suppress InvalidPropertyAssignmentValue * @var T */ - return $this->services[\spl_object_hash($name)] ??= $definition($this); + return $this + ->services + ->get($name) + ->match( + static fn($service) => $service, + function() use ($name, $definition) { + $service = $definition($this); + $this->services = $this->services->put($name, $service); + + return $service; + }, + ); } finally { $this->building = $this->building->dropEnd(1); } @@ -88,6 +98,10 @@ public function __invoke(Service $name): object */ public static function of(Map $definitions): self { - return new self($definitions, Sequence::of()); + return new self( + $definitions, + Sequence::of(), + Map::of(), + ); } } From a175fa2dad3aa378f41cf631b612fb54ed04b36b Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sat, 23 Aug 2025 22:14:53 +0200 Subject: [PATCH 2/2] free unused services from memory --- CHANGELOG.md | 4 ++++ src/Container.php | 9 +++++++-- tests/ContainerTest.php | 16 ++++++++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fd2d10..0781bc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Changed + +- Unused services are freed from memory + ### Removed - Using a `string` as a service name diff --git a/src/Container.php b/src/Container.php index 3e78f8c..d96e8a3 100644 --- a/src/Container.php +++ b/src/Container.php @@ -10,6 +10,7 @@ use Innmind\Immutable\{ Map, Sequence, + Maybe, Str, }; @@ -20,7 +21,7 @@ final class Container * * @param Map $definitions * @param Sequence $building - * @param Map $services + * @param Map> $services */ private function __construct( private Map $definitions, @@ -77,11 +78,15 @@ public function __invoke(Service $name): object return $this ->services ->get($name) + ->flatMap(static fn($service) => Maybe::of($service->get())) ->match( static fn($service) => $service, function() use ($name, $definition) { $service = $definition($this); - $this->services = $this->services->put($name, $service); + $this->services = $this->services->put( + $name, + \WeakReference::create($service), + ); return $service; }, diff --git a/tests/ContainerTest.php b/tests/ContainerTest.php index 47f7225..bd289ac 100644 --- a/tests/ContainerTest.php +++ b/tests/ContainerTest.php @@ -80,4 +80,20 @@ public function testEnumCaseCanBeUsedToReferenceAService() $this->assertSame($expected, $container(Services::a)); } + + public function testServicesAreFreedFromMemoryWhenUnused() + { + $called = 0; + $container = Builder::new() + ->add(Services::name, static function() use (&$called) { + ++$called; + + return new \stdClass; + }) + ->build(); + + $container(Services::name); + $this->assertInstanceOf(\stdClass::class, $container(Services::name)); + $this->assertSame(2, $called); + } }