Passed
Push — master ( 965bd1...a2c0fb )
by Sebastian
03:30
created

Container   A

Complexity

Total Complexity 32

Size/Duplication

Total Lines 322
Duplicated Lines 0 %

Test Coverage

Coverage 72.5%

Importance

Changes 5
Bugs 1 Features 2
Metric Value
wmc 32
eloc 72
c 5
b 1
f 2
dl 0
loc 322
ccs 58
cts 80
cp 0.725
rs 9.84

10 Methods

Rating   Name   Duplication   Size   Complexity  
A set() 0 3 1
A __construct() 0 3 1
A delete() 0 12 2
A get() 0 7 2
A has() 0 3 1
A buildTreeRecursive() 0 37 5
A buildObjects() 0 28 5
A resolve() 0 17 1
B buildTree() 0 60 9
A buildArguments() 0 26 5
1
<?php
2
3
/**
4
 * Linna Framework.
5
 *
6
 * @author Sebastian Rapetti <[email protected]>
7
 * @copyright (c) 2018, Sebastian Rapetti
8
 * @license http://opensource.org/licenses/MIT MIT License
9
 */
10
declare(strict_types=1);
11
12
namespace Linna\Container;
13
14
use ArrayAccess;
15
use Linna\Container\Exception\NotFoundException;
16
use Psr\Container\ContainerInterface;
17
use ReflectionClass;
18
use SplStack;
19
20
/**
21
 * Dependency Injection Container and Resolver.
22
 */
23
class Container implements ContainerInterface, ArrayAccess
24
{
25
    use PropertyAccessTrait;
26
    use ArrayAccessTrait;
27
28
    /**
29
     * @const string
30
     */
31
    private const NO_TYPE = 'NO_TYPE';
32
33
    /**
34
     * @var array<mixed> Contains object already resolved.
35
     */
36
    private array $cache = [];
37
38
    /**
39
     * @var array<mixed> Hierarchical structure of dependencies.
40
     */
41
    protected array $tree = [];
42
43
    /**
44
     * @var array<mixed> Rules for resolve scalar arguments or unexpected behaviors.
45
     */
46
    protected array $rules = [];
47
48
    /**
49
     * Class Constructor.
50
     *
51
     * @param array<mixed> $rules Rules for resolve scalar arguments or unexpected behaviors.
52
     */
53 93
    public function __construct(array $rules = [])
54
    {
55 93
        $this->rules = $rules;
56 93
    }
57
58
    /**
59
     * Finds an entry of the container by its identifier and returns it.
60
     *
61
     * @param string $id Identifier of the entry to look for.
62
     *
63
     * @throws NotFoundException No entry was found for **this** identifier.
64
     *
65
     * @return mixed Entry.
66
     */
67 36
    public function get($id)
68
    {
69 36
        if (isset($this->cache[$id])) {
70 18
            return $this->cache[$id];
71
        }
72
73 18
        throw new NotFoundException('No entry was found for this identifier.');
74
    }
75
76
    /**
77
     * Returns true if the container can return an entry for the given identifier.
78
     * Returns false otherwise.
79
     *
80
     * `has($id)` returning true does not mean that `get($id)` will not throw an exception.
81
     * It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`.
82
     *
83
     * @param string $id Identifier of the entry to look for.
84
     *
85
     * @return bool
86
     */
87 36
    public function has($id)
88
    {
89 36
        return isset($this->cache[$id]);
90
    }
91
92
    /**
93
     * Store a value inside container.
94
     *
95
     * @param string $id
96
     * @param mixed  $value
97
     *
98
     * @return void
99
     */
100 55
    public function set(string $id, $value): void
101
    {
102 55
        $this->cache[$id] = $value;
103 55
    }
104
105
    /**
106
     * Delete value from container.
107
     *
108
     * @param string $id
109
     *
110
     * @return bool
111
     */
112 24
    public function delete(string $id): bool
113
    {
114 24
        if (\array_key_exists($id, $this->cache)) {
115
116
            //delete value
117 18
            unset($this->cache[$id]);
118
119
            //return function result
120 18
            return true;
121
        }
122
123 6
        return false;
124
    }
125
126
    /**
127
     * Resolve dependencies for given class.
128
     *
129
     * @param string       $class An existing class.
130
     * @param array<mixed> $rules Custom rules.
131
     *
132
     * @return object|null Instance of resolved class
133
     */
134 15
    public function resolve(string $class, array $rules = []): ?object
135
    {
136
        //reset tree;
137 15
        $this->tree = [];
138
139
        //merge rules passed as parameter with general rules
140 15
        $this->rules = \array_merge($this->rules, $rules);
141
142
        //build dependency tree
143 15
        $this->buildTreeRecursive($class);
144
        //$this->buildTree($class);
145
146
        //build objects
147 15
        $this->buildObjects();
148
149
        //return required class
150 15
        return $this->cache[$class] ?? null;
151
    }
152
153
    /**
154
     * Create a map of dependencies for a class
155
     *
156
     * @param string    $class Class wich tree will build
157
     * @param int       $level Level for dependency
158
     *
159
     * @return void
160
     */
161 15
    private function buildTreeRecursive(string $class, int $level = 0): void
162
    {
163
        //initialize array if not already initialized
164 15
        $this->tree[$level][$class] = [];
165
166
        //get parameter from constructor
167
        //can return error when constructor not declared
168 15
        $constructor = (new ReflectionClass($class))->getConstructor();//->getParameters();
169
        //this should resolve the error when a class without constructor is encountered
170 15
        $parameters = \is_null($constructor) ? [] : $constructor->getParameters();
171
172
        //loop parameter
173 15
        foreach ($parameters as $param) {
174
175
            //Function ReflectionType::__toString() is deprecated
176
            //FROM https://www.php.net/manual/en/migration74.deprecated.php
177
            //Calls to ReflectionType::__toString() now generate a deprecation notice.
178
            //This method has been deprecated in favor of ReflectionNamedType::getName()
179
            //in the documentation since PHP 7.1, but did not throw a deprecation notice
180
            //for technical reasons.
181
            //Get the data type of the parameter
182 10
            $type = \is_null($param->getType()) ? self::NO_TYPE : $param->getType()->getName();
183
184
            //if there is parameter with callable type
185 10
            if (\class_exists($type)) {
186
187
                //store dependency
188 10
                $this->tree[$level][$class][] = $param;
189
190
                //call recursive, head recursion
191 10
                $this->buildTreeRecursive($type, $level + 1);
192
193
                //continue
194 10
                continue;
195
            }
196
197 2
            $this->tree[$level][$class][] = $param;
198
        }
199 15
    }
200
201
    /**
202
     * Create a dependencies map for a class.
203
     *
204
     * @param string $class
205
     *
206
     * @return void
207
     *
208
     * @deprecated since version 0.27.0 Return to recursive build of the tree
209
     *                                  because is 1.5x time fast.
210
     */
211
    private function buildTree(string $class): void
0 ignored issues
show
Unused Code introduced by
The method buildTree() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
212
    {
213
        $level = 0;
214
        $stack = new SplStack();
215
216
        while (true) {
217
218
            //initialize array if not already initialized
219
            $this->tree[$level][$class] ??= [];
220
221
            //get parameter from constructor
222
            //can return error when constructor not declared
223
            $constructor = (new ReflectionClass($class))->getConstructor();//->getParameters();
224
            //this should resolve the error when a class without constructor is encountered
225
            $parameters = \is_null($constructor) ? [] : $constructor->getParameters();
226
227
            //loop parameter
228
            foreach ($parameters as $param) {
229
230
                //check if argument is already stored
231
                $notAlreadyStored = !\in_array($param, $this->tree[$level][$class]);
232
233
                //Function ReflectionType::__toString() is deprecated
234
                //FROM https://www.php.net/manual/en/migration74.deprecated.php
235
                //Calls to ReflectionType::__toString() now generate a deprecation notice.
236
                //This method has been deprecated in favor of ReflectionNamedType::getName()
237
                //in the documentation since PHP 7.1, but did not throw a deprecation notice
238
                //for technical reasons.
239
                $type = ($param->getType() !== null) ? $param->getType()->getName() : self::NO_TYPE;
240
241
                //if there is parameter with callable type
242
                if (\class_exists($type) && $notAlreadyStored) {
243
244
                    //push values in stack for simulate later recursive function
245
                    $stack->push([$level, $class]);
246
247
                    //store dependency
248
                    $this->tree[$level][$class][] = $param;
249
250
                    //update values for simulate recursive function
251
                    $level++;
252
                    $class = $param->getClass()->name;
253
254
                    //return to main while
255
                    continue 2;
256
                }
257
258
                if ($notAlreadyStored) {
259
                    //store dependency
260
                    $this->tree[$level][$class][] = $param;
261
                }
262
            }
263
264
            //if stack is empty break while end exit from function
265
            if ($stack->count() === 0) {
266
                break;
267
            }
268
269
            //get last value pushed into stack;
270
            list($level, $class) = $stack->pop();
271
        }
272
    }
273
274
    /**
275
     * Build objects from dependencyTree.
276
     *
277
     * @return void
278
     */
279 15
    private function buildObjects(): void
280
    {
281
        //deep dependency level, start to array end for not use array_reverse
282 15
        for ($i = \count($this->tree) - 1; $i >= 0; $i--) {
283
284
            //class
285 15
            foreach ($this->tree[$i] as $class => $arguments) {
286
287
                //try to find object in class
288 15
                if (isset($this->cache[$class])) {
289
                    // no build need
290 6
                    continue;
291
                }
292
293
                //object is not in cache and need arguments try to build
294 15
                if (\count($arguments)) {
295
296
                    //build arguments
297 10
                    $args = $this->buildArguments($class, $arguments);
298
299
                    //store object with dependencies in cache
300 10
                    $this->cache[$class] = (new ReflectionClass($class))->newInstanceArgs($args);
301
302 10
                    continue;
303
                }
304
305
                //store object in cache
306 15
                $this->cache[$class] = (new ReflectionClass($class))->newInstance();
307
            }
308
        }
309 15
    }
310
311
    /**
312
     * Build dependency for a object.
313
     *
314
     * @param string       $class
315
     * @param array<mixed> $dependency
316
     *
317
     * @return array<mixed>
318
     */
319 10
    private function buildArguments(string $class, array $dependency): array
320
    {
321
        //initialize arguments array
322 10
        $args = [];
323
324
        //argument required from class
325 10
        foreach ($dependency as $argValue) {
326 10
            $argType = \is_null($argValue->getType()) ? self::NO_TYPE : $argValue->getType()->getName();
327
328 10
            if (\class_exists($argType)) {
329
                //add to array of arguments
330 10
                $paramClass = $argValue->getClass()->name;
331 10
                $args[] = $this->cache[$paramClass];
332 10
                continue;
333
            }
334
335 1
            $args[] = null;
336
        }
337
338
        //check if there is rules for this class
339 10
        if (isset($this->rules[$class])) {
340
            //merge arguments
341 1
            $args = \array_replace($args, $this->rules[$class]);
342
        }
343
344 10
        return $args;
345
    }
346
}
347