Completed
Push — dev-2.1 ( 1abc11...263f90 )
by Augusto
03:44 queued 02:02
created

Container::getItem()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 14
rs 9.2
c 0
b 0
f 0
cc 4
eloc 7
nc 3
nop 2
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
    public function __construct($configurator = null)
16
    {
17
        $this->configurator = $configurator;
18
    }
19
20
    public function __isset($name)
21
    {
22
        return $this->has($name);
23
    }
24
25
    public function has($name)
26
    {
27
        $this->configure();
28
29
        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
    public function __invoke($spec)
33
    {
34
        if (is_callable($spec)) {
35
            if (is_array($spec)) {
36
                list($class, $method) = $spec;
37
                $class = new ReflectionClass($class);
38
                $object = $class->newInstance();
39
                $mirror = $class->getMethod($method);
40
            } else {
41
                $object = false;
42
                $mirror = new ReflectionFunction($spec);
43
            }
44
            $container = $this;
45
            $arguments = array_map(
46
                function ($param) use ($container) {
47
                    if ($paramClass = $param->getClass()) {
48
                        $paramClassName = $paramClass->getName();
49
50
                        return $container->getItem($paramClassName);
51
                    }
52
                },
53
                $mirror->getParameters()
54
            );
55
            if ($object) {
56
                return $mirror->invokeArgs($object, $arguments);
57
            }
58
59
            return $mirror->invokeArgs($arguments);
60
        }
61
        if ((bool) array_filter(func_get_args(), 'is_object')) {
62
            foreach (func_get_args() as $dependency) {
63
                $this[get_class($dependency)] = $dependency;
64
            }
65
        }
66
67
        foreach ($spec as $name => $item) {
68
            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
        }
70
71
        $this->configure();
72
73
        return $this;
74
    }
75
76
    public function __call($name, $dict)
77
    {
78
        $this->__invoke($dict[0]);
79
80
        return $this->getItem($name);
81
    }
82
83
    protected function configure()
84
    {
85
        $configurator = $this->configurator;
86
        $this->configurator = null;
87
88
        if (is_null($configurator)) {
89
            return;
90
        }
91
92
        if (is_array($configurator)) {
93
            return $this->loadArray($configurator);
94
        }
95
96
        if (file_exists($configurator)) {
97
            return $this->loadFile($configurator);
98
        }
99
100
        if (is_string($configurator)) {
101
            return $this->loadString($configurator);
102
        }
103
104
        throw new Argument("Invalid input. Must be a valid file or array");
105
    }
106
107
    public function getItem($name, $raw = false)
108
    {
109
        $this->configure();
110
111
        if (!isset($this[$name])) {
112
            throw new NotFoundException("Item $name not found");
113
        }
114
115
        if ($raw || !is_callable($this[$name])) {
116
            return $this[$name];
117
        }
118
119
        return $this->lazyLoad($name);
120
    }
121
122
    public function get($name)
123
    {
124
        return $this->getItem($name);
125
    }
126
127
    public function loadString($configurator)
128
    {
129
        $configuration = preg_replace('/^[\s\t]+/', '', $configurator);
0 ignored issues
show
Unused Code introduced by
$configuration is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
130
        $iniData = parse_ini_string($configurator, true);
131
        if (false === $iniData || count($iniData) == 0) {
132
            throw new Argument("Invalid configuration string");
133
        }
134
135
        return $this->loadArray($iniData);
136
    }
137
138
    public function loadFile($configurator)
139
    {
140
        $iniData = parse_ini_file($configurator, true);
141
        if (false === $iniData) {
142
            throw new Argument("Invalid configuration INI file");
143
        }
144
145
        return $this->loadArray($iniData);
146
    }
147
148
    protected function state()
149
    {
150
        return array_filter($this->getArrayCopy(), function ($v) {
151
            return !is_object($v) || !$v instanceof Instantiator;
152
        });
153
    }
154
155
    public function loadArray(array $configurator)
156
    {
157
        foreach ($this->state() + $configurator as $key => $value) {
158
            if ($value instanceof \Closure) {
159
                continue;
160
            }
161
            $this->parseItem($key, $value);
162
        }
163
    }
164
165
    public function __get($name)
166
    {
167
        return $this->getItem($name);
168
    }
169
170
    public function __set($name, $value)
171
    {
172
        if (isset($this[$name]) && $this[$name] instanceof Instantiator) {
173
            $this[$name]->setInstance($value);
174
        }
175
        $this[$name] = $value;
176
    }
177
178
    protected function keyHasStateInstance($key, &$k)
179
    {
180
        return $this->offsetExists($k = current((explode(' ', $key))));
181
    }
182
183
    protected function keyHasInstantiator($key)
184
    {
185
        return false !== stripos($key, ' ');
186
    }
187
188
    protected function parseItem($key, $value)
189
    {
190
        $key = trim($key);
191
        if ($this->keyHasInstantiator($key)) {
192
            if ($this->keyHasStateInstance($key, $k)) {
193
                $this->offsetSet($key, $this[$k]);
194
            } else {
195
                $this->parseInstantiator($key, $value);
196
            }
197
        } else {
198
            $this->parseStandardItem($key, $value);
199
        }
200
    }
201
202
    protected function parseSubValues(&$value)
203
    {
204
        foreach ($value as &$subValue) {
205
            $subValue = $this->parseValue($subValue);
206
        }
207
208
        return $value;
209
    }
210
211
    protected function parseStandardItem($key, &$value)
212
    {
213
        if (is_array($value)) {
214
            $this->parseSubValues($value);
215
        } else {
216
            $value = $this->parseValue($value);
217
        }
218
219
        $this->offsetSet($key, $value);
220
    }
221
222
    protected function removeDuplicatedSpaces($string)
223
    {
224
        return preg_replace('/\s+/', ' ', $string);
225
    }
226
227
    protected function parseInstantiator($key, $value)
228
    {
229
        $key = $this->removeDuplicatedSpaces($key);
230
        list($keyName, $keyClass) = explode(' ', $key, 2);
231
        if ('instanceof' === $keyName) {
232
            $keyName = $keyClass;
233
        }
234
        $instantiator = new Instantiator($keyClass);
235
236
        if (is_array($value)) {
237
            foreach ($value as $property => $pValue) {
238
                $instantiator->setParam($property, $this->parseValue($pValue));
239
            }
240
        } else {
241
            $instantiator->setParam('__construct', $this->parseValue($value));
242
        }
243
244
        $this->offsetSet($keyName, $instantiator);
245
    }
246
247
    protected function parseValue($value)
248
    {
249
        if ($value instanceof Instantiator) {
250
            return $value;
251
        } elseif (is_array($value)) {
252
            return $this->parseSubValues($value);
253
        } elseif (empty($value)) {
254
            return null;
255
        } else {
256
            return $this->parseSingleValue($value);
257
        }
258
    }
259
    protected function hasCompleteBrackets($value)
260
    {
261
        return false !== strpos($value, '[') && false !== strpos($value, ']');
262
    }
263
264
    protected function parseSingleValue($value)
265
    {
266
        if (is_object($value)) {
267
            return $value;
268
        }
269
270
        $value = trim($value);
271
        if ($this->hasCompleteBrackets($value)) {
272
            return $this->parseBrackets($value);
273
        } else {
274
            return $this->parseConstants($value);
275
        }
276
    }
277
278
    protected function parseConstants($value)
279
    {
280
        if (preg_match('/^[\\a-zA-Z_]+([:]{2}[A-Z_]+)?$/', $value) && defined($value)) {
281
            return constant($value);
282
        } else {
283
            return $value;
284
        }
285
    }
286
287
    protected function matchSequence(&$value)
288
    {
289
        if (preg_match('/^\[(.*?,.*?)\]$/', $value, $match)) {
290
            return (boolean) ($value = $match[1]);
291
        }
292
    }
293
294
    protected function matchReference(&$value)
295
    {
296
        if (preg_match('/^\[([[:alnum:]_\\\\]+)\]$/', $value, $match)) {
297
            return (boolean) ($value = $match[1]);
298
        }
299
    }
300
301
    protected function parseBrackets($value)
302
    {
303
        if ($this->matchSequence($value)) {
304
            return $this->parseArgumentList($value);
305
        } elseif ($this->matchReference($value)) {
306
            return $this->getItem($value, true);
307
        } else {
308
            return $this->parseVariables($value);
309
        }
310
    }
311
312
    protected function parseVariables($value)
313
    {
314
        $self = $this;
315
316
        return preg_replace_callback(
317
            '/\[(\w+)\]/',
318
            function ($match) use (&$self) {
319
                return $self[$match[1]] ?: '';
320
            },
321
            $value
322
        );
323
    }
324
325
    protected function parseArgumentList($value)
326
    {
327
        $subValues = explode(',', $value);
328
329
        return $this->parseSubValues($subValues);
330
    }
331
332
    protected function lazyLoad($name)
333
    {
334
        $callback = $this[$name];
335
        if ($callback instanceof Instantiator && $callback->getMode() != Instantiator::MODE_FACTORY) {
336
            return $this[$name] = $callback();
337
        }
338
339
        return $callback();
340
    }
341
}
342