Issues (248)

src/Spinner/Container/Container.php (1 issue)

Labels
Severity
1
<?php
2
3
declare(strict_types=1);
4
5
namespace AlecRabbit\Spinner\Container;
6
7
use AlecRabbit\Spinner\Container\Contract\IContainer;
8
use AlecRabbit\Spinner\Container\Contract\IService;
9
use AlecRabbit\Spinner\Container\Contract\IServiceDefinition;
10
use AlecRabbit\Spinner\Container\Contract\IServiceSpawner;
11
use AlecRabbit\Spinner\Container\Contract\IServiceSpawnerFactory;
12
use AlecRabbit\Spinner\Container\Exception\ContainerException;
13
use AlecRabbit\Spinner\Container\Exception\NotFoundInContainer;
14
use ArrayObject;
15
use Psr\Container\ContainerExceptionInterface;
16
use Throwable;
17
use Traversable;
18
19
final readonly class Container implements IContainer
0 ignored issues
show
A parse error occurred: Syntax error, unexpected T_READONLY, expecting T_CLASS on line 19 at column 6
Loading history...
20
{
21
    private IServiceSpawner $serviceSpawner;
22
23
    /** @var ArrayObject<string, IServiceDefinition> */
24
    private ArrayObject $definitions;
25
26
    /** @var ArrayObject<string, IService> */
27
    private ArrayObject $services;
28
29
    /**
30
     * @param Traversable<IServiceDefinition>|null $definitions
31
     */
32
    public function __construct(
33
        IServiceSpawnerFactory $spawnerFactory,
34
        ?Traversable $definitions = null,
35
    ) {
36
        $this->serviceSpawner = $spawnerFactory->create($this);
37
38
        /** @psalm-suppress MixedPropertyTypeCoercion */
39
        $this->definitions = new ArrayObject();
40
        /** @psalm-suppress MixedPropertyTypeCoercion */
41
        $this->services = new ArrayObject();
42
43
        if ($definitions) {
44
            /**
45
             * @var IServiceDefinition $definition
46
             */
47
            foreach ($definitions as $definition) {
48
                $this->register($definition);
49
            }
50
        }
51
    }
52
53
    private function register(IServiceDefinition $definition): void
54
    {
55
        $id = $definition->getId();
56
57
        $this->assertNotRegistered($id);
58
        $this->definitions->offsetSet($id, $definition);
59
    }
60
61
    private function assertNotRegistered(string $id): void
62
    {
63
        if ($this->has($id)) {
64
            throw new ContainerException(
65
                sprintf(
66
                    'Definition with id "%s" already registered.',
67
                    $id,
68
                )
69
            );
70
        }
71
    }
72
73
    public function has(string $id): bool
74
    {
75
        return $this->hasDefinition($id);
76
    }
77
78
    private function hasDefinition(string $id): bool
79
    {
80
        return $this->definitions->offsetExists($id);
81
    }
82
83
    /**
84
     * @inheritDoc
85
     *
86
     * @psalm-suppress MixedInferredReturnType
87
     */
88
    public function get(string $id): mixed
89
    {
90
        if (!$this->has($id)) {
91
            throw new NotFoundInContainer(
92
                sprintf(
93
                    'There is no service with id "%s" in the container.',
94
                    $id,
95
                )
96
            );
97
        }
98
99
        if ($this->hasService($id)) {
100
            /** @psalm-suppress MixedReturnStatement */
101
            return $this->retrieveService($id)->getValue();
102
        }
103
104
        /** @psalm-suppress MixedReturnStatement */
105
        return $this->createService($id)->getValue();
106
    }
107
108
    private function hasService(string $id): bool
109
    {
110
        return $this->services->offsetExists($id);
111
    }
112
113
    private function retrieveService(string $id): IService
114
    {
115
        /** @psalm-suppress MixedReturnStatement */
116
        return $this->services->offsetGet($id);
117
    }
118
119
    /**
120
     * @throws ContainerExceptionInterface
121
     */
122
    private function createService(string $id): IService
123
    {
124
        $definition = $this->getDefinition($id);
125
126
        $service = $this->spawn($definition);
127
128
        if ($service->isStorable()) {
129
            $this->storeService($service);
130
        }
131
132
        return $service;
133
    }
134
135
    private function getDefinition(string $id): IServiceDefinition
136
    {
137
        return $this->definitions->offsetGet($id);
138
    }
139
140
    /**
141
     * @throws ContainerExceptionInterface
142
     */
143
    private function spawn(IServiceDefinition $definition): IService
144
    {
145
        try {
146
            return $this->serviceSpawner->spawn($definition);
147
        } catch (Throwable $e) {
148
            $details =
149
                sprintf(
150
                    '[%s]: "%s".',
151
                    get_debug_type($e),
152
                    $e->getMessage(),
153
                );
154
155
            throw new ContainerException(
156
                sprintf(
157
                    'Could not instantiate service. %s',
158
                    $details,
159
                ),
160
                previous: $e,
161
            );
162
        }
163
    }
164
165
    private function storeService(IService $service): void
166
    {
167
        $this->services->offsetSet($service->getId(), $service);
168
    }
169
}
170