Completed
Pull Request — master (#64)
by Augusto
02:13 queued 01:13
created

Container::__call()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 3
cts 3
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 2
crap 1
1
<?php
2
3
namespace Respect\Config;
4
5
use Respect\Config\InvalidArgumentException as Argument;
6
use ArrayObject;
7
use ReflectionClass;
8
use ReflectionFunction;
9
use Psr\Container\ContainerInterface;
10
11
class Container extends ArrayObject implements ContainerInterface
12
{
13
    protected $configurator;
14
15 63
    public function __construct($configurator = null)
16
    {
17 63
        $this->configurator = $configurator;
18 63
    }
19
20 2
    public function __isset($name)
21
    {
22 2
        return $this->has($name);
23
    }
24
25 3
    public function has($name)
26
    {
27 3
        $this->configure();
28
29 3
        return parent::offsetExists($name);
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (offsetExists() instead of has()). Are you sure this is correct? If so, you might want to change this to $this->offsetExists().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
30
    }
31
32 5
    public function __invoke($spec)
33
    {
34 5
        if (is_callable($spec)) {
35 3
            if (is_array($spec)) {
36 1
                list($class, $method) = $spec;
37 1
                $class = new ReflectionClass($class);
38 1
                $object = $class->newInstance();
39 1
                $mirror = $class->getMethod($method);
40 1
            } else {
41 2
                $object = false;
42 2
                $mirror = new ReflectionFunction($spec);
43
            }
44 3
            $container = $this;
45 3
            $arguments = array_map(
46
                function ($param) use ($container) {
47 3
                    if ($paramClass = $param->getClass()) {
48 3
                        $paramClassName = $paramClass->getName();
49
50 3
                        return $container->getItem($paramClassName);
51
                    }
52 3
                },
53 3
                $mirror->getParameters()
54 3
            );
55 3
            if ($object) {
56 1
                return $mirror->invokeArgs($object, $arguments);
57
            }
58
59 2
            return $mirror->invokeArgs($arguments);
60
        }
61 4
        if ((bool) array_filter(func_get_args(), 'is_object')) {
62 2
            foreach (func_get_args() as $dependency) {
63 2
                $this[get_class($dependency)] = $dependency;
64 2
            }
65 2
        }
66
67 4
        foreach ($spec as $name => $item) {
68 4
            parent::offsetSet($name, $item);
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (offsetSet() instead of __invoke()). Are you sure this is correct? If so, you might want to change this to $this->offsetSet().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
69 4
        }
70
71 4
        $this->configure();
72
73 4
        return $this;
74
    }
75
76 1
    public function __call($name, $dict)
77
    {
78 1
        $this->__invoke($dict[0]);
79
80 1
        return $this->getItem($name);
81
    }
82
83 60
    protected function configure()
84
    {
85 60
        $configurator = $this->configurator;
86 60
        $this->configurator = null;
87
88 60
        if (is_null($configurator)) {
89 38
            return;
90
        }
91
92 49
        if (is_array($configurator)) {
93 2
            return $this->loadArray($configurator);
94
        }
95
96 47
        if (file_exists($configurator)) {
97 1
            return $this->loadFile($configurator);
98
        }
99
100 46
        if (is_string($configurator)) {
101 42
            return $this->loadString($configurator);
102
        }
103
104 4
        throw new Argument("Invalid input. Must be a valid file or array");
105
    }
106
107 60
    public function getItem($name, $raw = false)
108
    {
109 60
        $this->configure();
110
111 55
        if (!isset($this[$name])) {
112 8
            throw new NotFoundException("Item $name not found");
113
        }
114
115 47
        if ($raw || !is_callable($this[$name])) {
116 39
            return $this[$name];
117
        }
118
119 21
        return $this->lazyLoad($name);
120
    }
121
122 4
    public function get($name)
123
    {
124 4
        return $this->getItem($name);
125
    }
126
127 43
    public function loadString($configurator)
128
    {
129 43
        $configurator = preg_replace('/^[\s\t]+/', '', $configurator);
130 43
        $iniData = parse_ini_string($configurator, true);
131 43
        if (false === $iniData || count($iniData) == 0) {
132 1
            throw new Argument("Invalid configuration string");
133
        }
134
135 42
        return $this->loadArray($iniData);
136
    }
137
138 1
    public function loadFile($configurator)
139
    {
140 1
        $iniData = parse_ini_file($configurator, true);
141 1
        if (false === $iniData) {
142
            throw new Argument("Invalid configuration INI file");
143
        }
144
145 1
        return $this->loadArray($iniData);
146
    }
147
148 52
    protected function state()
149
    {
150
        return array_filter($this->getArrayCopy(), function ($v) {
151 7
            return !is_object($v) || !$v instanceof Instantiator;
152 52
        });
153
    }
154
155 52
    public function loadArray(array $configurator)
156
    {
157 52
        foreach ($this->state() + $configurator as $key => $value) {
158 52
            if ($value instanceof \Closure) {
159 1
                continue;
160
            }
161 52
            $this->parseItem($key, $value);
162 51
        }
163 47
    }
164
165 45
    public function __get($name)
166
    {
167 45
        return $this->getItem($name);
168
    }
169
170 7
    public function __set($name, $value)
171
    {
172 7
        if (isset($this[$name]) && $this[$name] instanceof Instantiator) {
173
            $this[$name]->setInstance($value);
174
        }
175 7
        $this[$name] = $value;
176 7
    }
177
178 22
    protected function keyHasStateInstance($key, &$k)
179
    {
180 22
        return $this->offsetExists($k = current((explode(' ', $key))));
181
    }
182
183 52
    protected function keyHasInstantiator($key)
184
    {
185 52
        return false !== stripos($key, ' ');
186
    }
187
188 52
    protected function parseItem($key, $value)
189
    {
190 52
        $key = trim($key);
191 52
        if ($this->keyHasInstantiator($key)) {
192 22
            if ($this->keyHasStateInstance($key, $k)) {
193 2
                $this->offsetSet($key, $this[$k]);
194 2
            } else {
195 22
                $this->parseInstantiator($key, $value);
196
            }
197 22
        } else {
198 34
            $this->parseStandardItem($key, $value);
199
        }
200 51
    }
201
202 15
    protected function parseSubValues(&$value)
203
    {
204 15
        foreach ($value as &$subValue) {
205 15
            $subValue = $this->parseValue($subValue);
206 15
        }
207
208 15
        return $value;
209
    }
210
211 34
    protected function parseStandardItem($key, &$value)
212
    {
213 34
        if (is_array($value)) {
214 3
            $this->parseSubValues($value);
215 3
        } else {
216 32
            $value = $this->parseValue($value);
217
        }
218
219 33
        $this->offsetSet($key, $value);
220 33
    }
221
222 22
    protected function removeDuplicatedSpaces($string)
223
    {
224 22
        return preg_replace('/\s+/', ' ', $string);
225
    }
226
227 22
    protected function parseInstantiator($key, $value)
228
    {
229 22
        $key = $this->removeDuplicatedSpaces($key);
230 22
        list($keyName, $keyClass) = explode(' ', $key, 2);
231 22
        if ('instanceof' === $keyName) {
232 2
            $keyName = $keyClass;
233 2
        }
234 22
        $instantiator = new Instantiator($keyClass);
235
236 22
        if (is_array($value)) {
237 21
            foreach ($value as $property => $pValue) {
238 17
                $instantiator->setParam($property, $this->parseValue($pValue));
239 21
            }
240 21
        } else {
241 1
            $instantiator->setParam('__construct', $this->parseValue($value));
242
        }
243
244 22
        $this->offsetSet($keyName, $instantiator);
245 22
    }
246
247 48
    protected function parseValue($value)
248
    {
249 48
        if ($value instanceof Instantiator) {
250
            return $value;
251 48
        } elseif (is_array($value)) {
252 7
            return $this->parseSubValues($value);
253 48
        } elseif (empty($value)) {
254 8
            return null;
255
        } else {
256 46
            return $this->parseSingleValue($value);
257
        }
258
    }
259 46
    protected function hasCompleteBrackets($value)
260
    {
261 46
        return false !== strpos($value, '[') && false !== strpos($value, ']');
262
    }
263
264 46
    protected function parseSingleValue($value)
265
    {
266 46
        if (is_object($value)) {
267 2
            return $value;
268
        }
269
270 46
        $value = trim($value);
271 46
        if ($this->hasCompleteBrackets($value)) {
272 27
            return $this->parseBrackets($value);
273
        } else {
274 38
            return $this->parseConstants($value);
275
        }
276
    }
277
278 38
    protected function parseConstants($value)
279
    {
280 38
        if (preg_match('/^[\\a-zA-Z_]+([:]{2}[A-Z_]+)?$/', $value) && defined($value)) {
281 2
            return constant($value);
282
        } else {
283 37
            return $value;
284
        }
285
    }
286
287 27
    protected function matchSequence(&$value)
288
    {
289 27
        if (preg_match('/^\[(.*?,.*?)\]$/', $value, $match)) {
290 9
            return (boolean) ($value = $match[1]);
291
        }
292 21
    }
293
294 21
    protected function matchReference(&$value)
295
    {
296 21
        if (preg_match('/^\[([[:alnum:]_\\\\]+)\]$/', $value, $match)) {
297 20
            return (boolean) ($value = $match[1]);
298
        }
299 1
    }
300
301 27
    protected function parseBrackets($value)
302
    {
303 27
        if ($this->matchSequence($value)) {
304 9
            return $this->parseArgumentList($value);
305 21
        } elseif ($this->matchReference($value)) {
306 20
            return $this->getItem($value, true);
307
        } else {
308 1
            return $this->parseVariables($value);
309
        }
310
    }
311
312 1
    protected function parseVariables($value)
313
    {
314 1
        $self = $this;
315
316 1
        return preg_replace_callback(
317 1
            '/\[(\w+)\]/',
318 1
            function ($match) use (&$self) {
319 1
                return $self[$match[1]] ?: '';
320 1
            },
321
            $value
322 1
        );
323
    }
324
325 9
    protected function parseArgumentList($value)
326
    {
327 9
        $subValues = explode(',', $value);
328
329 9
        return $this->parseSubValues($subValues);
330
    }
331
332 21
    protected function lazyLoad($name)
333
    {
334 21
        $callback = $this[$name];
335 21
        if ($callback instanceof Instantiator && $callback->getMode() != Instantiator::MODE_FACTORY) {
336 18
            return $this[$name] = $callback();
337
        }
338
339 3
        return $callback();
340
    }
341
}
342