Passed
Push — master ( 3e81fd...ba09db )
by Jonathan
04:14
created

Aware   A

Complexity

Total Complexity 37

Size/Duplication

Total Lines 321
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
eloc 72
dl 0
loc 321
ccs 93
cts 93
cp 1
rs 9.44
c 0
b 0
f 0
wmc 37

19 Methods

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