Test Setup Failed
Push — master ( 43de71...09fe49 )
by
unknown
02:34
created

Container::resolveMethodParameters()   C

Complexity

Conditions 11
Paths 10

Size

Total Lines 37
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 37
rs 5.2653
cc 11
eloc 24
nc 10
nop 1

How to fix   Complexity   

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
 * (c) Paulus Gandung Prakosa ([email protected])
5
 */
6
7
namespace DependencyInjection;
8
9
use \ArrayAccess;
10
11
class Container implements \ArrayAccess
12
{
13
    /**
14
     * @var array
15
     */
16
    private $bindings = [];
17
18
    /**
19
     * @var array
20
     */
21
    private $resolved = [];
22
23
    /**
24
     * @var array
25
     */
26
    private $aliases = [];
27
28
    /**
29
     * Resolving all dependencies in the supplied class or object instance constructor.
30
     *
31
     * @param string $instance The class name.
32
     * @param array $parameters List of needed class dependency.
33
     * @return object
34
     */
35
    public function make($instance, $parameters = [])
36
    {
37
        if ($this->isAbstractExists($instance)) {
38
            return $this->resolve($instance, $this->getConcrete($instance));
39
        }
40
41
        return $this->resolve($instance, is_array($parameters) ? $parameters
42
            : array_slice(func_get_args(), 1));
43
    }
44
45
    /**
46
     * Register a service alias.
47
     *
48
     * @param string $alias The alias name.
49
     * @param string $abstract The class name.
50
     */
51
    public function register($alias, $abstract)
52
    {
53
        if (!is_string($alias) || !is_string($abstract)) {
54
            throw new \InvalidArgumentException(
55
                sprintf("Parameter 1 and 2 of %s must be a string.", __METHOD__)
56
            );
57
        }
58
59
        if (!isset($this->aliases[$alias])) {
60
            $this->aliases[$alias] = $this->make($abstract);
61
        }
62
63
        return $this;
64
    }
65
66
    /**
67
     * Determine if registered alias were exists.
68
     *
69
     * @param string $alias The alias name.
70
     */
71
    public function isAliasExists($alias)
72
    {
73
        return isset($this->aliases[$alias]);
74
    }
75
76
    /**
77
     * Get concrete implementation from supplied alias name.
78
     *
79
     * @param string $alias The alias name.
80
     */
81
    public function get($alias)
82
    {
83
        if (!$this->isAliasExists($alias)) {
84
            throw new \InvalidArgumentException(
85
                sprintf("Parameter 1 of %s must be valid alias name.", __METHOD__)
86
            );
87
        }
88
89
        return $this->aliases[$alias];
90
    }
91
    
92
    /**
93
     * {@inheritdoc}
94
     */
95
    public function offsetExists($offset)
96
    {
97
        return $this->isBound($offset);
98
    }
99
100
    /**
101
     * {@inheritdoc}
102
     */
103
    public function offsetGet($offset)
104
    {
105
        return $this->make($offset);
106
    }
107
108
    /**
109
     * {@inheritdoc}
110
     */
111
    public function offsetSet($offset, $value)
112
    {
113
        $this->bind($offset, $value instanceof \Closure ? $value : $this->turnIntoResolvableClosure($offset, $value));
114
    }
115
116
    /**
117
     * {@inheritdoc}
118
     */
119
    public function offsetUnset($offset)
120
    {
121
        unset($this->bindings[$offset], $this->resolved[$offset]);
122
    }
123
124
    /**
125
     * Get list of unresolved class name from class binding stack.
126
     *
127
     * @return string
128
     */
129
    protected function getAbstracts()
130
    {
131
        return array_keys($this->bindings);
132
    }
133
134
    /**
135
     * Determine if unresolved class name is exists.
136
     *
137
     * @param string $abstract The unresolved class name.
138
     * @return bool
139
     */
140
    public function isAbstractExists($abstract)
141
    {
142
        return array_key_exists($abstract, $this->bindings);
143
    }
144
145
    /**
146
     * Determine if concrete dependency is exists.
147
     *
148
     * @param mixed $concrete The concrete dependency.
149
     * @return bool
150
     */
151
    public function isConcreteExists($concrete)
152
    {
153
        foreach (array_values($this->bindings) as $value) {
154
            if (in_array($concrete, $value, true)) {
155
                $isConcreteExists = true;
156
157
                break;
158
            }
159
        }
160
161
        return (isset($isConcreteExists) ? $isConcreteExists : false);
162
    }
163
164
    public function isInterface($abstract)
165
    {
166
        $reflector = Internal\ReflectionClassFactory::create($abstract);
167
168
        return $reflector->isInterface();
169
    }
170
171
    /**
172
     * Get concrete list of dependencies based on supplied class name.
173
     *
174
     * @param string $abstract The unresolved class name.
175
     * @return array
176
     */
177
    public function getConcrete($abstract)
178
    {
179
        return ($this->isAbstractExists($abstract) ? $this->bindings[$abstract] : null);
180
    }
181
182
    /**
183
     * Resolve class dependencies in the supplied class name.
184
     *
185
     * @param string $instance The class name.
186
     * @param array $parameters The needed class dependency.
187
     * @return object
188
     */
189
    protected function resolve($instance, $parameters = [])
190
    {
191
        $reflector = Internal\ReflectionClassFactory::create($instance);
192
193
        if (!$this->hasConstructor($reflector)) {
194
            $this->markAsResolved($instance);
195
            
196
            return $this->resolveInstanceWithoutConstructor($reflector);
197
        }
198
199
        if (is_array($parameters) && empty(sizeof($parameters))) {
200
            $constructorParams = $this->getMethodParameters($reflector, '__construct');
201
202
            if (!is_null($constructorParams)) {
203
                $params = $this->resolveMethodParameters($constructorParams);
204
            }
205
        } elseif (is_array($parameters) && !empty(sizeof($parameters))) {
206
            $params = $this->resolveMethodParameters($parameters);
207
        }
208
209
        $this->markAsResolved($instance);
210
211
        return $reflector->newInstanceArgs($params);
0 ignored issues
show
Bug introduced by
The variable $params does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
212
    }
213
214
    /**
215
     * Resolve method parameters.
216
     *
217
     * @param array $params The unresolvable method.
218
     * @return array
219
     */
220
    protected function resolveMethodParameters($params = [])
221
    {
222
        if (!is_array($params)) {
223
            throw new \InvalidArgumentException(
224
                sprintf("Parameter 1 of %s must be an array.", __METHOD__)
225
            );
226
        }
227
228
        foreach ($params as $key => $value) {
229
            if ($value instanceof \ReflectionParameter) {
230
                $class = $value->getClass();
231
232
                if ($class instanceof \ReflectionClass) {
233
                    if ($class->isInterface()) {
234
                        $params[$key] = $this->getConcreteFromInterface($class->getName());
0 ignored issues
show
Bug introduced by
Consider using $class->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
235
236
                        $this->markAsResolved($class->getName());
0 ignored issues
show
Bug introduced by
Consider using $class->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
237
                    } else {
238
                        $params[$key] = $this->circularDependencyResolver($class->getName());
0 ignored issues
show
Bug introduced by
Consider using $class->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
239
                    }
240
                } else {
241
                    $params[$key] = ($value->isDefaultValueAvailable()
242
                        ? $value->getDefaultValue() : null);
243
                }
244
            } else {
245
                if (is_string($value) && class_exists($value)) {
246
                    $params[$key] = $this->circularDependencyResolver($value);
247
                } elseif ($value instanceof \Closure) {
248
                    $params[$key] = ($this->isConcreteExists($value) ? $value($this) : $value);
249
                } else {
250
                    $params[$key] = $value;
251
                }
252
            }
253
        }
254
255
        return $params;
256
    }
257
258
    /**
259
     * Recursively resolving class dependency.
260
     *
261
     * @param string $class The valid class name.
262
     * @return object
263
     */
264
    protected function circularDependencyResolver($class)
265
    {
266
        if (!is_string($class) && !class_exists($class)) {
267
            throw Internal\Exception\ReflectionExceptionFactory::invalidArgument(
268
                sprintf("Parameter 1 of %s must be a string of valid class name.", __METHOD__)
269
            );
270
        }
271
272
        $reflector = Internal\ReflectionClassFactory::create($class);
273
274
        if (!$this->hasConstructor($reflector)) {
275
            return $this->resolveInstanceWithoutConstructor($reflector);
276
        } else {
277
            $param = $this->getMethodParameters($reflector, '__construct');
278
279
            if (empty($param)) {
280
                return $reflector->newInstance();
281
            } else {
282
                foreach ($param as $key => $value) {
283
                    $class = $value->getClass();
284
285
                    if ($class instanceof \ReflectionClass) {
286
                        if ($class->isInterface()) {
287
                            $param[$key] = $this->getConcreteFromInterface($class->getName());
0 ignored issues
show
Bug introduced by
Consider using $class->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
288
289
                            $this->markAsResolved($class->getName());
0 ignored issues
show
Bug introduced by
Consider using $class->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
290
                        } else {
291
                            $param[$key] = $this->circularDependencyResolver($class->getName());
0 ignored issues
show
Bug introduced by
Consider using $class->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
292
                        }
293
                    }
294
                }
295
296
                return $reflector->newInstanceArgs($param);
297
            }
298
        }
299
    }
300
301
    /**
302
     * Get concrete implementation from abstract.
303
     *
304
     * @param string $interface The interface name.
305
     * @return object
306
     */
307
    protected function getConcreteFromInterface($interface)
308
    {
309
        if (!$this->isAbstractExists($interface)) {
310
            throw Internal\Exception\ReflectionExceptionFactory::runtime(
311
                sprintf("%s has no concrete implementation in the clas binding stack.", $interface)
312
            );
313
        }
314
315
        if (sizeof($this->bindings[$interface]) > 1) {
316
            throw Internal\Exception\ReflectionExceptionFactory::logic(
317
                "An interface must only have 1 concrete implementation."
318
            );
319
        }
320
321
        $concrete = $this->bindings[$interface][0];
322
323
        return ($concrete instanceof \Closure ? $concrete($this)
324
            : (is_string($concrete) && class_exists($concrete)
325
                ? $this->resolve($concrete) : $concrete));
326
    }
327
328
    /**
329
     * Determine if current reflection object has constructor.
330
     *
331
     * @param \ReflectionClass $refl The current reflection class object.
332
     * @return boolean
333
     */
334
    protected function hasConstructor(Internal\ReflectionClassFactory $refl)
335
    {
336
        return $refl->hasMethod('__construct');
337
    }
338
339
    /**
340
     * Determine if unresolvable class name has cloneable.
341
     *
342
     * @param \ReflectionClass $refl The current reflection class object.
343
     * @return boolean
344
     */
345
    protected function isCloneable(Internal\ReflectionClassFactory $refl)
346
    {
347
        return $refl->hasMethod('__clone');
348
    }
349
350
    /**
351
     * Determine if unresolvable class name has serializable.
352
     *
353
     * @param \ReflectionClass $refl The current reflection class object.
354
     * @return boolean
355
     */
356
    protected function isSerializable(Internal\ReflectionClassFactory $refl)
357
    {
358
        return $refl->hasMethod('__sleep');
359
    }
360
361
    /**
362
     * Resolving class name without constructor.
363
     *
364
     * @param \ReflectionClass $refl An instance of \ReflectionClass
365
     */
366
    protected function resolveInstanceWithoutConstructor(Internal\ReflectionClassFactory $refl)
367
    {
368
        return $refl->newInstanceWithoutConstructor();
369
    }
370
371
    /**
372
     * Get method parameters.
373
     *
374
     * @param \ReflectionClass $refl An reflection class instance.
375
     * @param string $method The method name.
376
     * @return array
377
     */
378
    protected function getMethodParameters(Internal\ReflectionClassFactory $refl, $method)
379
    {
380
        return ($refl->hasMethod($method) ? $refl->getMethod($method)->getParameters() : null);
381
    }
382
383
    /**
384
     * Mark resolved class name to true.
385
     *
386
     * @param string $abstract The resolved class name.
387
     * @return void
388
     */
389
    protected function markAsResolved($abstract)
390
    {
391
        if ($this->isAbstractExists($abstract)) {
392
            $this->resolved[$abstract] = true;
393
394
            unset($this->bindings[$abstract]);
395
        }
396
    }
397
398
    /**
399
     * Bind service into binding container stack.
400
     *
401
     * @param string $abstract The unresolvable class name.
402
     * @param \Closure|string $concrete Closure or class name being bound to the class name.
403
     */
404
    public function bind($abstract, $concrete = null)
405
    {
406
        if (is_null($concrete)) {
407
            $concrete = $abstract;
408
        }
409
410
        if (!($concrete instanceof \Closure)) {
411
            $concrete = $this->turnIntoResolvableClosure($abstract, $concrete);
412
        }
413
414
        if (isset($this->bindings[$abstract])) {
415
            array_push($this->bindings[$abstract], $concrete);
416
        } else {
417
            $this->bindings[$abstract] = [$concrete];
418
        }
419
    }
420
421
    /**
422
     * Bind service into binding container stack if supplied class name
423
     * not being bound.
424
     *
425
     * @param string $abstract The unresolvable class name.
426
     * @param \Closure|string $concrete Closure or class name begin bound to the class name.
427
     */
428
    public function bindIf($abstract, $concrete)
429
    {
430
        if (!$this->isBound($abstract)) {
431
            $this->bind($abstract, $concrete);
432
        }
433
    }
434
435
    /**
436
     * Call defined instance.
437
     *
438
     * @param string $instance The class name to invoke/call.
439
     * @param array $args The class name __invoke method argument.
440
     * @return mixed|void
441
     */
442
    public function callInstance($instance, $args = [])
443
    {
444
        $args = (is_array($args) ? $args : array_slice(func_get_args(), 1));
445
        
446
        $current = $this->make($instance);
447
448
        $this->markAsResolved($instance);
449
450
        return call_user_func_array($current, $args);
451
    }
452
453
    /**
454
     * Determine if class name has been bound or not.
455
     *
456
     * @param string $abstract The unresolvable class name.
457
     * @return bool
458
     */
459
    public function isBound($abstract)
460
    {
461
        return $this->isAbstractExists($abstract);
462
    }
463
464
    /**
465
     * Turn class name into resolvable closure.
466
     *
467
     * @param string $abstract The class name
468
     * @param \Closure|string $concrete Can be instance of \Closure or class name.
469
     * @return \Closure
470
     */
471
    protected function turnIntoResolvableClosure($abstract, $concrete)
472
    {
473
        return function (Container $container, $parameters = []) use ($abstract, $concrete) {
474
            return ($abstract == $concrete ? $container->resolve($abstract)
475
                : $container->resolve($concrete, $parameters));
0 ignored issues
show
Bug introduced by
It seems like $concrete defined by parameter $concrete on line 471 can also be of type object<Closure>; however, DependencyInjection\Container::resolve() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
476
        };
477
    }
478
}
479