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 56805ff..d96e8a3 100644 --- a/src/Container.php +++ b/src/Container.php @@ -10,23 +10,23 @@ use Innmind\Immutable\{ Map, Sequence, + Maybe, Str, }; 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 +75,22 @@ 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) + ->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, + \WeakReference::create($service), + ); + + return $service; + }, + ); } finally { $this->building = $this->building->dropEnd(1); } @@ -88,6 +103,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(), + ); } } 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); + } }