Skip to content

Commit f2caabd

Browse files
committed
feat(singleton): add singleton method
1 parent f9a5cc3 commit f2caabd

3 files changed

Lines changed: 129 additions & 9 deletions

File tree

src/Container.php

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,32 +15,56 @@
1515

1616
final class Container implements ContainerInterface
1717
{
18-
/** @var array<class-string, class-string|callable(ContainerInterface $container): object> */
18+
/**
19+
* @var array<class-string, array{
20+
* concrete: object|class-string|callable(ContainerInterface $container): object,
21+
* isSingleton: bool
22+
* }>
23+
*/
1924
private array $entries = [];
2025

2126
/**
2227
* @template T of object
2328
* @param class-string<T> $id
2429
* @return T
2530
* @throws NotFoundExceptionInterface
31+
* @throws ContainerExceptionInterface
2632
*/
2733
public function get(string $id): object
2834
{
2935
if ($this->has($id)) {
30-
$entry = $this->entries[$id];
36+
[
37+
'concrete' => $concrete,
38+
'isSingleton' => $isSingleton
39+
] = $this->entries[$id];
3140

32-
if (is_callable($entry)) {
41+
if (is_callable($concrete)) {
3342
/** @var T */
34-
$concrete = $entry($this);
43+
$object = $concrete($this);
3544

36-
return $concrete;
45+
if ($isSingleton) {
46+
$this->singleton($id, $object);
47+
}
48+
49+
return $object;
50+
}
51+
52+
if (is_string($concrete)) {
53+
/** @var T */
54+
$object = $this->resolve($concrete);
55+
56+
$this->singleton($id, $object);
57+
58+
return $object;
3759
}
3860

39-
$id = $entry;
61+
if (is_object($concrete) && $isSingleton) {
62+
return $concrete;
63+
}
4064
}
4165

4266
/** @var T */
43-
$object = $this->resolve($id);
67+
$object = $this->resolve($concrete ?? $id);
4468

4569
return $object;
4670
}
@@ -56,9 +80,27 @@ public function has(string $id): bool
5680
* @param class-string<T> $id
5781
* @param class-string<T>|callable(ContainerInterface $container): T $concrete
5882
*/
59-
public function set(string $id, $concrete): void
83+
public function set(string $id, $concrete): self
6084
{
61-
$this->entries[$id] = $concrete;
85+
$this->entries[$id]['concrete'] = $concrete;
86+
$this->entries[$id]['isSingleton'] = false;
87+
88+
return $this;
89+
}
90+
91+
/**
92+
* @template T of object
93+
* @param class-string<T>|T $id
94+
*/
95+
public function singleton($id): self
96+
{
97+
$fqcn = is_object($id) ? get_class($id) : $id;
98+
$concrete = func_num_args() === 2 ? func_get_arg(1) : $id;
99+
100+
$this->entries[$fqcn]['concrete'] = $concrete;
101+
$this->entries[$fqcn]['isSingleton'] = true;
102+
103+
return $this;
62104
}
63105

64106
/**

src/Overloads/Container.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace flight;
6+
7+
use Psr\Container\ContainerExceptionInterface;
8+
use Psr\Container\ContainerInterface;
9+
use Psr\Container\NotFoundExceptionInterface;
10+
11+
final class Container implements ContainerInterface
12+
{
13+
/**
14+
* @template T of object
15+
* @param class-string<T> $id
16+
* @return T
17+
* @throws NotFoundExceptionInterface
18+
* @throws ContainerExceptionInterface
19+
*/
20+
public function get(string $id): object
21+
{
22+
if (!$this->has($id)) {
23+
throw new NotFoundException;
24+
}
25+
26+
return new $id;
27+
}
28+
29+
/** @param class-string $id */
30+
public function has(string $id): bool
31+
{
32+
return false;
33+
}
34+
35+
/**
36+
* @template T of object
37+
* @param class-string<T> $id
38+
* @param null|class-string<T>|callable(ContainerInterface $container): T $concrete
39+
*/
40+
public function singleton(string $id, $concrete = null): self
41+
{
42+
return $this;
43+
}
44+
45+
/**
46+
* @template T of object
47+
* @param class-string<T> $id
48+
* @param class-string<T>|callable(ContainerInterface $container): T $concrete
49+
*/
50+
public function set(string $id, $concrete): self
51+
{
52+
return $this;
53+
}
54+
}

tests/ContainerTest.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,30 @@ public function test_it_resolves_optional_non_builtin_classes_constructor_parame
151151
);
152152
}
153153

154+
public function test_can_set_a_singleton_from_fqcn(): void
155+
{
156+
$container = new Container;
157+
158+
$container->singleton(DateTimeImmutable::class);
159+
160+
$dateTimeImmutable = $container->get(DateTimeImmutable::class);
161+
$dateTimeImmutable2 = $container->get(DateTimeImmutable::class);
162+
163+
self::assertSame($dateTimeImmutable, $dateTimeImmutable2);
164+
}
165+
166+
public function test_can_set_an_implementation_as_singleton(): void
167+
{
168+
$container = new Container;
169+
170+
$container->singleton(ExampleInterface::class, ExampleImplementation::class);
171+
172+
$exampleImplementation = $container->get(ExampleInterface::class);
173+
$exampleImplementation2 = $container->get(ExampleInterface::class);
174+
175+
self::assertSame($exampleImplementation, $exampleImplementation2);
176+
}
177+
154178
/** @return array{0: class-string}[] */
155179
public static function nonInstantiableClassStringsDataProvider(): array
156180
{

0 commit comments

Comments
 (0)