Passed
Push — master ( 79ce76...898f07 )
by Jonathan
03:45
created

Container::getInstance()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
ccs 0
cts 2
cp 0
rs 10
cc 1
nc 1
nop 0
crap 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Gravatalonga\Container;
6
7
use ArrayAccess;
8
use Closure;
9
use Psr\Container\ContainerInterface;
10
use ReflectionClass;
11
use ReflectionException;
12
use ReflectionFunction;
13
use ReflectionParameter;
14
use Reflector;
15
16
use function array_key_exists;
17
use function is_callable;
18
19
/**
20
 * Class Container.
21
 */
22
class Container extends AutoWiringAware implements ArrayAccess, ContainerInterface
23
{
24
    /**
25
     * @var ContainerInterface
26
     */
27
    protected static $instance;
28
29
    /**
30
     * @var array<string, string>
31
     */
32
    private $aliases = [];
33
34
    /**
35
     * @var array<string, mixed>
36
     */
37
    private $bindings;
38
39
    /**
40
     * @var array<string, mixed>
41
     */
42
    private $resolved = [];
43
44
    /**
45
     * @var array<string, mixed>
46
     */
47
    private $share;
48
49
    /**
50
     * Container constructor.
51
     *
52
     * @param array<string, mixed> $config
53
     *
54
     * @throws NotFoundContainerException
55
     */
56 51
    public function __construct(array $config = [])
57
    {
58 51
        $this->bindings = $config;
59 51
        $this->share = [];
60
61 51
        $self = $this;
62
        $this->share(ContainerInterface::class, static function () use ($self) {
63 18
            return $self;
64 51
        });
65 51
        $this->alias(ContainerInterface::class, Container::class);
66 51
    }
67
68
    /**
69
     * @param string $entry
70
     * @param string $alias
71
     *
72
     * @throws NotFoundContainerException
73
     *
74
     * @return void
75
     */
76 51
    public function alias($entry, $alias)
77
    {
78 51
        if (false === $this->has($entry)) {
79 3
            throw NotFoundContainerException::entryNotFound($entry);
80
        }
81
82 51
        $this->aliases[$alias] = $entry;
83 51
    }
84
85
    /**
86
     * Factory binding.
87
     *
88
     * @param string $id
89
     * @param callable|mixed $factory
90
     *
91
     * @return void
92
     */
93 30
    public function factory(string $id, $factory)
94
    {
95 30
        $this->bindings[$id] = is_callable($factory) ?
96 21
            ($factory instanceof Closure ?
97 21
                $factory :
98 21
                Closure::fromCallable($factory)) :
99 12
            $factory;
100 30
    }
101
102
    /**
103
     * {@inheritdoc}
104
     *
105
     * @throws ReflectionException
106
     */
107 33
    public function get($id)
108
    {
109 33
        return $this->resolve($id, []);
110
    }
111
112
    /**
113
     * @return ContainerInterface
114
     */
115
    public static function getInstance()
116
    {
117
        return self::$instance;
118
    }
119
120
    /**
121
     * {@inheritdoc}
122
     */
123 51
    public function has($id)
124
    {
125 51
        return array_key_exists($id, $this->bindings) ||
126 51
            array_key_exists($id, $this->share) ||
127 51
            array_key_exists($id, $this->aliases);
128
    }
129
130
    /**
131
     * @param string $id
132
     *
133
     * @return bool
134
     */
135 3
    public function isAlias($id)
136
    {
137 3
        return true === array_key_exists($id, $this->aliases);
138
    }
139
140
    /**
141
     * @param string $id
142
     * @param array<string, mixed> $arguments
143
     *
144
     * @throws NotFoundContainerException
145
     * @throws ContainerException|ReflectionException
146
     *
147
     * @return mixed|object
148
     */
149 18
    public function make($id, array $arguments = [])
150
    {
151 18
        if (array_key_exists($id, $this->share)) {
152 3
            throw ContainerException::shareOnMake($id);
153
        }
154
155 15
        return $this->resolve($id, $arguments);
156
    }
157
158
    /**
159
     * @param string $offset
160
     *
161
     * @return bool
162
     */
163 6
    public function offsetExists($offset)
164
    {
165 6
        return $this->has($offset);
166
    }
167
168
    /**
169
     * @param string $offset
170
     *
171
     * @throws ReflectionException
172
     *
173
     * @return mixed
174
     */
175 6
    public function offsetGet($offset)
176
    {
177 6
        return $this->get($offset);
178
    }
179
180
    /**
181
     * @param string $offset
182
     * @param mixed $value
183
     */
184 9
    public function offsetSet($offset, $value): void
185
    {
186 9
        $this->factory($offset, $value);
187 9
    }
188
189
    /**
190
     * @param string $offset
191
     *
192
     * @return void
193
     */
194 3
    public function offsetUnset($offset)
195
    {
196
        unset(
197 3
            $this->bindings[$offset],
198 3
            $this->share[$offset],
199 3
            $this->resolved[$offset],
200 3
            $this->aliases[$offset]
201
        );
202 3
    }
203
204
    /**
205
     * Alias for Factory method.
206
     *
207
     * @param string $id
208
     * @param mixed $factory
209
     *
210
     * @return void
211
     */
212 15
    public function set(string $id, $factory)
213
    {
214 15
        $this->factory($id, $factory);
215 15
    }
216
217
    /**
218
     * @param ContainerInterface $container
219
     */
220
    public static function setInstance(ContainerInterface $container): void
221
    {
222
        self::$instance = $container;
223
    }
224
225
    /**
226
     * Share rather resolve as factory.
227
     *
228
     * @param string $id
229
     * @param Closure $factory
230
     *
231
     * @return void
232
     */
233 51
    public function share($id, Closure $factory)
234
    {
235 51
        if (true === array_key_exists($id, $this->resolved)) {
236
            unset($this->resolved[$id]);
237
        }
238 51
        $this->share[$id] = $factory;
239 51
    }
240
241
    /**
242
     * @param ReflectionParameter[] $params
243
     * @param array<string, mixed> $arguments
244
     *
245
     * @return array<int, string>
246
     */
247 36
    private function buildDependencies(array $params, array $arguments = [])
248
    {
249 36
        return array_map(
250
            function (ReflectionParameter $param) use ($arguments) {
251 21
                if (true === array_key_exists($param->getName(), $arguments)) {
252 6
                    return $arguments[$param->getName()];
253
                }
254
255 18
                return $this->autoWiringArguments($param);
256 36
            },
257 12
            $params
258
        );
259
    }
260
261
    /**
262
     * @param string $id
263
     * @param array<string, mixed> $arguments
264
     *
265
     * @throws NotFoundContainerException
266
     * @throws ReflectionException
267
     *
268
     * @return mixed|object
269
     */
270 39
    private function resolve(string $id, array $arguments = [])
271
    {
272 39
        if (true === array_key_exists($id, $this->resolved)) {
273 9
            return $this->resolved[$id];
274
        }
275
276 39
        if (true === array_key_exists($id, $this->aliases)) {
277 12
            return $this->resolve($this->aliases[$id], $arguments);
278
        }
279
280 39
        if ((false === $this->has($id)) && (true === class_exists($id))) {
281 9
            return $this->resolveClass($id, $arguments);
282
        }
283
284 33
        if (true === $this->has($id)) {
285 33
            return $this->resolveEntry($id, $arguments);
286
        }
287
288
        throw NotFoundContainerException::entryNotFound($id);
289
    }
290
291
    /**
292
     * @param Reflector $reflection
293
     * @param array<string, mixed> $arguments
294
     *
295
     * @return array<int, mixed>
296
     */
297 36
    private function resolveArguments(Reflector $reflection, array $arguments = [])
298
    {
299 36
        $params = [];
300
301 36
        if ($reflection instanceof ReflectionClass) {
302 9
            if (null !== $constructor = $reflection->getConstructor()) {
303 6
                $params = $constructor->getParameters();
304
            }
305
        }
306
307 36
        if ($reflection instanceof ReflectionFunction) {
308 27
            $params = $reflection->getParameters();
309
        }
310
311 36
        return $this->buildDependencies($params, $arguments);
312
    }
313
314
    /**
315
     * @param mixed $id
316
     * @param array<string, mixed> $arguments
317
     *
318
     * @throws ReflectionException
319
     *
320
     * @return object
321
     */
322 9
    private function resolveClass($id, array $arguments = [])
323
    {
324 9
        $reflection = new ReflectionClass($id);
325
326 9
        return $reflection->newInstanceArgs($this->resolveArguments($reflection, $arguments));
327
    }
328
329
    /**
330
     * @param string $id
331
     * @param array<string, mixed> $arguments
332
     *
333
     * @throws ReflectionException
334
     *
335
     * @return mixed
336
     */
337 33
    private function resolveEntry(string $id, array $arguments = [])
338
    {
339 33
        $get = $this->bindings[$id] ?? $this->share[$id];
340
341 33
        if ($get instanceof Closure) {
342 27
            $reflection = new ReflectionFunction($get);
343 27
            $value = $reflection->invokeArgs($this->resolveArguments($reflection, $arguments));
344
345 27
            if (true === array_key_exists($id, $this->share)) {
346 24
                $this->resolved[$id] = $value;
347
            }
348
349 27
            return $value;
350
        }
351
352 6
        return $get;
353
    }
354
}
355