Completed
Pull Request — master (#34)
by Sebastian
02:37
created

Container::buildTree()   B

Complexity

Conditions 9
Paths 17

Size

Total Lines 57
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 9

Importance

Changes 0
Metric Value
dl 0
loc 57
ccs 21
cts 21
cp 1
rs 7.0745
c 0
b 0
f 0
cc 9
eloc 20
nc 17
nop 1
crap 9

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * Linna Framework.
5
 *
6
 * @author Sebastian Rapetti <[email protected]>
7
 * @copyright (c) 2017, Sebastian Rapetti
8
 * @license http://opensource.org/licenses/MIT MIT License
9
 */
10
declare(strict_types=1);
11
12
namespace Linna\DI;
13
14
use Linna\DI\Exception\NotFoundException;
15
use Psr\Container\ContainerInterface;
16
17
/**
18
 * Dependency Injection Container and Resolver.
19
 */
20
class Container implements ContainerInterface, \ArrayAccess
21
{
22
    use PropertyAccessTrait;
23
    use ArrayAccessTrait;
24
25
    /**
26
     * @var array Contains object already resolved
27
     */
28
    private $cache = [];
29
30
    /**
31
     * @var array A map for resolve dependencies
32
     */
33
    private $tree = [];
34
35
    /**
36
     * @var array For resolve scalar arguments and unexpected behavior
37
     */
38
    private $rules = [];
39
40
    /**
41
     * {@inheritdoc}
42
     */
43 36
    public function get($key)
44
    {
45 36
        if (isset($this->cache[$key])) {
46 18
            return $this->cache[$key];
47
        }
48
49 18
        throw new NotFoundException('No entry was found for this identifier');
50
    }
51
52
    /**
53
     * {@inheritdoc}
54
     */
55 36
    public function has($key)
56
    {
57 36
        return isset($this->cache[$key]);
58
    }
59
60
    /**
61
     * Store a value inside container.
62
     *
63
     * @param string   $key
64
     * @param callable $value
65
     */
66 55
    public function set(string $key, $value)
67
    {
68 55
        $this->cache[$key] = $value;
69 55
    }
70
71
    /**
72
     * Delete value from container.
73
     *
74
     * @param string $key
75
     */
76 24
    public function delete(string $key) : bool
77
    {
78 24
        if (array_key_exists($key, $this->cache)) {
79
80
            //delete value
81 18
            unset($this->cache[$key]);
82
83
            //return function result
84 18
            return true;
85
        }
86
87 6
        return false;
88
    }
89
90
    /**
91
     * Set rules for unserolvable classes.
92
     *
93
     * @param array $rules
94
     */
95 1
    public function setRules(array $rules)
96
    {
97 1
        $this->rules = $rules;
98 1
    }
99
100
    /**
101
     * Resolve dependencies for given class.
102
     *
103
     * @param string $class
104
     * @param array  $rules
105
     *
106
     * @return object|null Instance of resolved class
107
     */
108 11
    public function resolve(string $class, array $rules = [])
109
    {
110
        //merge rules passed as parameter with general rules
111 11
        $this->rules = array_merge($this->rules, $rules);
112
113
        //build dependency tree
114 11
        $this->buildTree($class);
115
116
        //build objects
117 11
        $this->buildObjects();
118
119
        //return required class
120 11
        return $this->cache[$class] ?? null;
121
    }
122
123
    /**
124
     * Create a dependencies map for a class.
125
     *
126
     * @param string $class
127
     */
128 11
    private function buildTree(string $class)
129
    {
130
        //set start level
131 11
        $level = 0;
132
133
        //create stack
134 11
        $stack = new \SplStack();
135
136
        //iterate
137 11
        while (true) {
138
139
            //initialize array if not already initialized
140 11
            if (!isset($this->tree[$level][$class])) {
141 11
                $this->tree[$level][$class] = [];
142
            }
143
144
            //get parameter from constructor
145 11
            $parameters = (new \ReflectionClass($class))->getConstructor()->getParameters();
146
147
            //loop parameter
148 11
            foreach ($parameters as $param) {
149
150
                //check if argument is already stored
151 7
                $notAlreadyStored = !in_array($param, $this->tree[$level][$class]);
152
153
                //if there is parameter with callable type
154 7
                if (class_exists((string) $param->getType()) && $notAlreadyStored) {
155
156
                    //push values in stack for simulate later recursive function
157 7
                    $stack->push([$level, $class]);
158
159
                    //store dependency
160 7
                    $this->tree[$level][$class][] = $param;
161
162
                    //update values for simulate recursive function
163 7
                    $level++;
164 7
                    $class = (is_object($param->getClass())) ? $param->getClass()->name : null;
165
166
                    //return to main while
167 7
                    continue 2;
168
                }
169
170 7
                if ($notAlreadyStored) {
171
                    //store dependency
172 7
                    $this->tree[$level][$class][] = $param;
173
                }
174
            }
175
176
            //if stack is empty break while end exit from function
177 11
            if ($stack->count() === 0) {
178 11
                break;
179
            }
180
181
            //get last value pushed into stack;
182 7
            list($level, $class) = $stack->pop();
183
        }
184 11
    }
185
186
    /**
187
     * Build objects from dependencyTree.
188
     */
189 11
    private function buildObjects()
190
    {
191
        //deep dependency level, start to array end for not use array_reverse
192 11
        for ($i = count($this->tree) - 1; $i >= 0; $i--) {
193
194
            //class
195 11
            foreach ($this->tree[$i] as $class => $arguments) {
196
197
                //try to find object in class
198 11
                $object = $this->cache[$class] ?? null;
199
200
                //if object is not in cache and need arguments try to build
201 11
                if ($object === null && count($arguments) > 0) {
202
203
                    //build arguments
204 7
                    $args = $this->buildArguments($class, $arguments);
205
206
                    //store object with dependencies in cache
207 7
                    $this->cache[$class] = (new \ReflectionClass($class))->newInstanceArgs($args);
208
209 7
                    continue;
210
                }
211
212 11
                if ($object === null) {
213
214
                    //store object in cache
215 11
                    $this->cache[$class] = (new \ReflectionClass($class))->newInstance();
216
                }
217
            }
218
        }
219 11
    }
220
221
    /**
222
     * Build dependency for a object.
223
     *
224
     * @param string $class
225
     * @param array  $dependency
226
     *
227
     * @return array
228
     */
229 7
    private function buildArguments(string $class, array $dependency): array
230
    {
231
        //initialize arguments array
232 7
        $args = [];
233
234
        //argument required from class
235 7
        foreach ($dependency as $argValue) {
236 7
            $paramClass = (is_object($argValue->getClass())) ? $argValue->getClass()->name : null;
237
            //add to array of arguments
238 7
            $args[] = $this->cache[$paramClass] ?? null;
239
        }
240
241
        //check if there is rules for this class
242 7
        if (isset($this->rules[$class])) {
243
            //merge arguments
244 1
            $args = array_replace($args, $this->rules[$class]);
245
        }
246
247 7
        return $args;
248
    }
249
}
250