Completed
Push — master ( 99b4c1...ecde4d )
by Todd
03:17 queued 01:39
created

Container::makeRule()   C

Complexity

Conditions 19
Paths 12

Size

Total Lines 50
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 26
CRAP Score 19

Importance

Changes 0
Metric Value
dl 0
loc 50
ccs 26
cts 26
cp 1
rs 5.3267
c 0
b 0
f 0
cc 19
eloc 26
nc 12
nop 1
crap 19

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
 * @author Todd Burry <[email protected]>
4
 * @copyright 2009-2017 Vanilla Forums Inc.
5
 * @license MIT
6
 */
7
8
namespace Garden\Container;
9
10
use Interop\Container\ContainerInterface;
11
12
/**
13
 * An inversion of control container.
14
 */
15
class Container implements ContainerInterface {
16
    private $currentRule;
17
    private $currentRuleName;
18
    private $instances;
19
    private $rules;
20
    private $factories;
21
22
    /**
23
     * Construct a new instance of the {@link Container} class.
24
     */
25 87
    public function __construct() {
26 87
        $this->rules = ['*' => ['inherit' => true, 'constructorArgs' => null]];
27 87
        $this->instances = [];
28 87
        $this->factories = [];
29
30 87
        $this->rule('*');
31 87
    }
32
33
    /**
34
     * Deep clone rules.
35
     */
36 1
    public function __clone() {
37 1
        $this->rules = $this->arrayClone($this->rules);
38 1
        $this->rule($this->currentRuleName);
39 1
    }
40
41
    /**
42
     * Deep clone an array.
43
     *
44
     * @param array $array The array to clone.
45
     * @return array Returns the cloned array.
46
     * @see http://stackoverflow.com/a/17729234
47
     */
48 1
    private function arrayClone(array $array) {
49
        return array_map(function ($element) {
50 1
            return ((is_array($element))
51 1
                ? $this->arrayClone($element)
52 1
                : ((is_object($element))
53
                    ? clone $element
54 1
                    : $element
55
                )
56
            );
57 1
        }, $array);
58
    }
59
60
    /**
61
     * Normalize a container entry ID.
62
     *
63
     * @param string $id The ID to normalize.
64
     * @return string Returns a normalized ID as a string.
65
     */
66 88
    private function normalizeID($id) {
67 88
        return ltrim($id, '\\');
68
    }
69
70
    /**
71
     * Set the current rule to the default rule.
72
     *
73
     * @return $this
74
     */
75 1
    public function defaultRule() {
76 1
        return $this->rule('*');
77
    }
78
79
    /**
80
     * Set the current rule.
81
     *
82
     * @param string $id The ID of the rule.
83
     * @return $this
84
     */
85 87
    public function rule($id) {
86 87
        $id = $this->normalizeID($id);
87
88 87
        if (!isset($this->rules[$id])) {
89 43
            $this->rules[$id] = [];
90
        }
91 87
        $this->currentRuleName = $id;
92 87
        $this->currentRule = &$this->rules[$id];
93
94 87
        return $this;
95
    }
96
97
    /**
98
     * Get the class name of the current rule.
99
     *
100
     * @return string Returns a class name.
101
     */
102 2
    public function getClass() {
103 2
        return empty($this->currentRule['class']) ? '' : $this->currentRule['class'];
104
    }
105
106
    /**
107
     * Set the name of the class for the current rule.
108
     *
109
     * @param string $className A valid class name.
110
     * @return $this
111
     */
112 6
    public function setClass($className) {
113 6
        $this->currentRule['class'] = $className;
114 6
        return $this;
115
    }
116
117
    /**
118
     * Get the rule that the current rule references.
119
     *
120
     * @return string Returns a reference name or an empty string if there is no reference.
121
     */
122 3
    public function getAliasOf() {
123 3
        return empty($this->currentRule['aliasOf']) ? '' : $this->currentRule['aliasOf'];
124
    }
125
126
    /**
127
     * Set the rule that the current rule is an alias of.
128
     *
129
     * @param string $alias The name of an entry in the container to point to.
130
     * @return $this
131
     */
132 4
    public function setAliasOf($alias) {
133 4
        $alias = $this->normalizeID($alias);
134
135 4
        if ($alias === $this->currentRuleName) {
136 1
            trigger_error("You cannot set alias '$alias' to itself.", E_USER_NOTICE);
137
        } else {
138 3
            $this->currentRule['aliasOf'] = $alias;
139
        }
140 4
        return $this;
141
    }
142
143
    /**
144
     * Add an alias of the current rule.
145
     *
146
     * Setting an alias to the current rule means that getting an item with the alias' name will be like getting the item
147
     * with the current rule. If the current rule is shared then the same shared instance will be returned. You can add
148
     * multiple aliases by passing additional arguments to this method.
149
     *
150
     * If {@link Container::addAlias()} is called with an alias that is the same as the current rule then an **E_USER_NOTICE**
151
     * level error is raised and the alias is not added.
152
     *
153
     * @param string ...$alias The alias to set.
154
     * @return $this
155
     * @since 1.4 Added the ability to pass multiple aliases.
156
     */
0 ignored issues
show
Documentation introduced by
Should the type for parameter $alias not be string[]?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
157 8
    public function addAlias(...$alias) {
158 8
        foreach ($alias as $name) {
159 8
            $name = $this->normalizeID($name);
160
161 8
            if ($name === $this->currentRuleName) {
162 1
                trigger_error("Tried to set alias '$name' to self.", E_USER_NOTICE);
163
            } else {
164 8
                $this->rules[$name]['aliasOf'] = $this->currentRuleName;
165
            }
166
        }
167 8
        return $this;
168
    }
169
170
    /**
171
     * Remove an alias of the current rule.
172
     *
173
     * If {@link Container::removeAlias()} is called with an alias that references a different rule then an **E_USER_NOTICE**
174
     * level error is raised, but the alias is still removed.
175
     *
176
     * @param string $alias The alias to remove.
177
     * @return $this
178
     */
179 2
    public function removeAlias($alias) {
180 2
        $alias = $this->normalizeID($alias);
181
182 2
        if (!empty($this->rules[$alias]['aliasOf']) && $this->rules[$alias]['aliasOf'] !== $this->currentRuleName) {
183 1
            trigger_error("Alias '$alias' does not point to the current rule.", E_USER_NOTICE);
184
        }
185
186 2
        unset($this->rules[$alias]['aliasOf']);
187 2
        return $this;
188
    }
189
190
    /**
191
     * Get all of the aliases of the current rule.
192
     *
193
     * This method is intended to aid in debugging and should not be used in production as it walks the entire rule array.
194
     *
195
     * @return array Returns an array of strings representing aliases.
196
     */
197 6
    public function getAliases() {
198 6
        $result = [];
199
200 6
        foreach ($this->rules as $name => $rule) {
201 6
            if (!empty($rule['aliasOf']) && $rule['aliasOf'] === $this->currentRuleName) {
202 6
                $result[] = $name;
203
            }
204
        }
205
206 6
        return $result;
207
    }
208
209
    /**
210
     * Get the factory callback for the current rule.
211
     *
212
     * @return callable|null Returns the rule's factory or **null** if it has none.
213
     */
214 2
    public function getFactory() {
215 2
        return isset($this->currentRule['factory']) ? $this->currentRule['factory'] : null;
216
    }
217
218
    /**
219
     * Set the factory that will be used to create the instance for the current rule.
220
     *
221
     * @param callable $factory This callback will be called to create the instance for the rule.
222
     * @return $this
223
     */
224 10
    public function setFactory(callable $factory) {
225 10
        $this->currentRule['factory'] = $factory;
226 10
        return $this;
227
    }
228
229
    /**
230
     * Whether or not the current rule is shared.
231
     *
232
     * @return bool Returns **true** if the rule is shared or **false** otherwise.
233
     */
234 2
    public function isShared() {
235 2
        return !empty($this->currentRule['shared']);
236
    }
237
238
    /**
239
     * Set whether or not the current rule is shared.
240
     *
241
     * @param bool $shared Whether or not the current rule is shared.
242
     * @return $this
243
     */
244 38
    public function setShared($shared) {
245 38
        $this->currentRule['shared'] = $shared;
246 38
        return $this;
247
    }
248
249
    /**
250
     * Whether or not the current rule will inherit to subclasses.
251
     *
252
     * @return bool Returns **true** if the current rule inherits or **false** otherwise.
253
     */
254 2
    public function getInherit() {
255 2
        return !empty($this->currentRule['inherit']);
256
    }
257
258
    /**
259
     * Set whether or not the current rule extends to subclasses.
260
     *
261
     * @param bool $inherit Pass **true** to have subclasses inherit this rule or **false** otherwise.
262
     * @return $this
263
     */
264 3
    public function setInherit($inherit) {
265 3
        $this->currentRule['inherit'] = $inherit;
266 3
        return $this;
267
    }
268
269
    /**
270
     * Get the constructor arguments for the current rule.
271
     *
272
     * @return array Returns the constructor arguments for the current rule.
273
     */
274 2
    public function getConstructorArgs() {
275 2
        return empty($this->currentRule['constructorArgs']) ? [] : $this->currentRule['constructorArgs'];
276
    }
277
278
    /**
279
     * Set the constructor arguments for the current rule.
280
     *
281
     * @param array $args An array of constructor arguments.
282
     * @return $this
283
     */
284 25
    public function setConstructorArgs(array $args) {
285 25
        $this->currentRule['constructorArgs'] = $args;
286 25
        return $this;
287
    }
288
289
    /**
290
     * Set a specific shared instance into the container.
291
     *
292
     * When you set an instance into the container then it will always be returned by subsequent retrievals, even if a
293
     * rule is configured that says that instances should not be shared.
294
     *
295
     * @param string $name The name of the container entry.
296
     * @param mixed $instance This instance.
297
     * @return $this
298
     */
299 9
    public function setInstance($name, $instance) {
300 9
        $this->instances[$this->normalizeID($name)] = $instance;
301 9
        return $this;
302
    }
303
304
    /**
305
     * Add a method call to a rule.
306
     *
307
     * @param string $method The name of the method to call.
308
     * @param array $args The arguments to pass to the method.
309
     * @return $this
310
     */
311 6
    public function addCall($method, array $args = []) {
312 6
        $this->currentRule['calls'][] = [$method, $args];
313
314 6
        return $this;
315
    }
316
317
    /**
318
     * Finds an entry of the container by its identifier and returns it.
319
     *
320
     * @param string $id Identifier of the entry to look for.
321
     * @param array $args Additional arguments to pass to the constructor.
322
     *
323
     * @throws NotFoundException No entry was found for this identifier.
324
     * @throws ContainerException Error while retrieving the entry.
325
     *
326
     * @return mixed Entry.
327
     */
328 68
    public function getArgs($id, array $args = []) {
329 68
        $id = $this->normalizeID($id);
330
331 68
        if (isset($this->instances[$id])) {
332
            // A shared instance just gets returned.
333 16
            return $this->instances[$id];
334
        }
335
336 64
        if (isset($this->factories[$id])) {
337
            // The factory for this object type is already there so call it to create the instance.
338 4
            return $this->factories[$id]($args);
339
        }
340
341 64
        if (!empty($this->rules[$id]['aliasOf'])) {
342
            // This rule references another rule.
343 3
            return $this->getArgs($this->rules[$id]['aliasOf'], $args);
344
        }
345
346
        // The factory or instance isn't registered so do that now.
347
        // This call also caches the instance or factory fo faster access next time.
348 64
        return $this->createInstance($id, $args);
349
    }
350
351
    /**
352
     * Make a rule based on an ID.
353
     *
354
     * @param string $nid A normalized ID.
355
     * @return array Returns an array representing a rule.
356
     */
357 64
    private function makeRule($nid) {
358 64
        $rule = isset($this->rules[$nid]) ? $this->rules[$nid] : [];
359
360 64
        if (class_exists($nid)) {
361 56
            for ($class = get_parent_class($nid); !empty($class); $class = get_parent_class($class)) {
362
                // Don't add the rule if it doesn't say to inherit.
363 6
                if (!isset($this->rules[$class]) || (isset($this->rules[$class]['inherit']) && !$this->rules[$class]['inherit'])) {
364 4
                    break;
365
                }
366 2
                $rule += $this->rules[$class];
367
            }
368
369
            // Add the default rule.
370 56
            if (!empty($this->rules['*']['inherit'])) {
371 56
                $rule += $this->rules['*'];
372
            }
373
374
            // Add interface calls to the rule.
375 56
            $interfaces = class_implements($nid);
376 56
            foreach ($interfaces as $interface) {
377 41
                if (isset($this->rules[$interface])) {
378 10
                    $interfaceRule = $this->rules[$interface];
379
380 10
                    if (isset($interfaceRule['inherit']) && $interfaceRule['inherit'] === false) {
381 1
                        continue;
382
                    }
383
384 9
                    if (!isset($rule['shared']) && isset($interfaceRule['shared'])) {
385 3
                        $rule['shared'] = $interfaceRule['shared'];
386
                    }
387
388 9
                    if (!isset($rule['constructorArgs']) && isset($interfaceRule['constructorArgs'])) {
389 3
                        $rule['constructorArgs'] = $interfaceRule['constructorArgs'];
390
                    }
391
392 9
                    if (!empty($interfaceRule['calls'])) {
393 2
                        $rule['calls'] = array_merge(
394 2
                            isset($rule['calls']) ? $rule['calls'] : [],
395 56
                            $interfaceRule['calls']
396
                        );
397
                    }
398
                }
399
            }
400 10
        } elseif (!empty($this->rules['*']['inherit'])) {
401
            // Add the default rule.
402 10
            $rule += $this->rules['*'];
403
        }
404
405 64
        return $rule;
406
    }
407
408
    /**
409
     * Make a function that creates objects from a rule.
410
     *
411
     * @param string $nid The normalized ID of the container item.
412
     * @param array $rule The resolved rule for the ID.
413
     * @return \Closure Returns a function that when called will create a new instance of the class.
414
     * @throws NotFoundException No entry was found for this identifier.
415
     */
416 42
    private function makeFactory($nid, array $rule) {
417 42
        $className = empty($rule['class']) ? $nid : $rule['class'];
418
419 42
        if (!empty($rule['factory'])) {
420
            // The instance is created with a user-supplied factory function.
421 6
            $callback = $rule['factory'];
422 6
            $function = $this->reflectCallback($callback);
423
424 6
            if ($function->getNumberOfParameters() > 0) {
425 3
                $callbackArgs = $this->makeDefaultArgs($function, (array)$rule['constructorArgs'], $rule);
426
                $factory = function ($args) use ($callback, $callbackArgs) {
427 3
                    return call_user_func_array($callback, $this->resolveArgs($callbackArgs, $args));
428 3
                };
429
            } else {
430 3
                $factory = $callback;
431
            }
432
433
            // If a class is specified then still reflect on it so that calls can be made against it.
434 6
            if (class_exists($className)) {
435 6
                $class = new \ReflectionClass($className);
436
            }
437
        } else {
438
            // The instance is created by newing up a class.
439 36
            if (!class_exists($className)) {
440 1
                throw new NotFoundException("Class $className does not exist.", 404);
441
            }
442 35
            $class = new \ReflectionClass($className);
443 35
            $constructor = $class->getConstructor();
444
445 35
            if ($constructor && $constructor->getNumberOfParameters() > 0) {
446 32
                $constructorArgs = $this->makeDefaultArgs($constructor, (array)$rule['constructorArgs'], $rule);
447
448
                $factory = function ($args) use ($className, $constructorArgs) {
449 32
                    return new $className(...array_values($this->resolveArgs($constructorArgs, $args)));
450 32
                };
451
            } else {
452
                $factory = function () use ($className) {
453 5
                    return new $className;
454 5
                };
455
            }
456
        }
457
458
        // Add calls to the factory.
459 41
        if (isset($class) && !empty($rule['calls'])) {
460 4
            $calls = [];
461
462
            // Generate the calls array.
463 4
            foreach ($rule['calls'] as $call) {
464 4
                list($methodName, $args) = $call;
465 4
                $method = $class->getMethod($methodName);
466 4
                $calls[] = [$methodName, $this->makeDefaultArgs($method, $args, $rule)];
467
            }
468
469
            // Wrap the factory in one that makes the calls.
470
            $factory = function ($args) use ($factory, $calls) {
471 4
                $instance = $factory($args);
472
473 4
                foreach ($calls as $call) {
474 4
                    call_user_func_array(
475 4
                        [$instance, $call[0]],
476 4
                        $this->resolveArgs($call[1], [], $instance)
477
                    );
478
                }
479
480 4
                return $instance;
481 4
            };
482
        }
483
484 41
        return $factory;
485
    }
486
487
    /**
488
     * Create a shared instance of a class from a rule.
489
     *
490
     * This method has the side effect of adding the new instance to the internal instances array of this object.
491
     *
492
     * @param string $nid The normalized ID of the container item.
493
     * @param array $rule The resolved rule for the ID.
494
     * @param array $args Additional arguments passed during creation.
495
     * @return object Returns the the new instance.
0 ignored issues
show
Documentation introduced by
Should the return type not be integer|double|string|nu...boolean|resource|object?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
496
     * @throws NotFoundException Throws an exception if the class does not exist.
497
     */
498 23
    private function createSharedInstance($nid, array $rule, array $args) {
499 23
        if (!empty($rule['factory'])) {
500
            // The instance is created with a user-supplied factory function.
501 3
            $callback = $rule['factory'];
502 3
            $function = $this->reflectCallback($callback);
503
504 3
            if ($function->getNumberOfParameters() > 0) {
505 1
                $callbackArgs = $this->resolveArgs(
506 1
                    $this->makeDefaultArgs($function, (array)$rule['constructorArgs'], $rule),
507
                    $args
508
                );
509
510 1
                $this->instances[$nid] = null; // prevent cyclic dependency from infinite loop.
511 1
                $this->instances[$nid] = $instance = call_user_func_array($callback, $callbackArgs);
512
            } else {
513 2
                $this->instances[$nid] = $instance = $callback();
514
            }
515
516
            // Reflect on the instance so that calls can be made against it.
517 3
            if (is_object($instance)) {
518 3
                $class = new \ReflectionClass(get_class($instance));
519
            }
520
        } else {
521 20
            $className = empty($rule['class']) ? $nid : $rule['class'];
522 20
            if (!class_exists($className)) {
523 1
                throw new NotFoundException("Class $className does not exist.", 404);
524
            }
525 19
            $class = new \ReflectionClass($className);
526 19
            $constructor = $class->getConstructor();
527
528 19
            if ($constructor && $constructor->getNumberOfParameters() > 0) {
529
                // Instantiate the object first so that this instance can be used for cyclic dependencies.
530 18
                $this->instances[$nid] = $instance = $class->newInstanceWithoutConstructor();
531
532 18
                $constructorArgs = $this->resolveArgs(
533 18
                    $this->makeDefaultArgs($constructor, (array)$rule['constructorArgs'], $rule),
534
                    $args
535
                );
536 17
                $constructor->invokeArgs($instance, $constructorArgs);
537
            } else {
538 3
                $this->instances[$nid] = $instance = new $class->name;
539
            }
540
        }
541
542
        // Call subsequent calls on the new object.
543 21
        if (isset($class) && !empty($rule['calls'])) {
544 2
            foreach ($rule['calls'] as $call) {
545 2
                list($methodName, $args) = $call;
546 2
                $method = $class->getMethod($methodName);
547
548 2
                $args = $this->resolveArgs(
549 2
                    $this->makeDefaultArgs($method, $args, $rule),
550 2
                    [],
551
                    $instance
552
                );
553
554 2
                $method->invokeArgs($instance, $args);
555
            }
556
        }
557
558 21
        return $instance;
559
    }
560
561
    /**
562
     * Make an array of default arguments for a given function.
563
     *
564
     * @param \ReflectionFunctionAbstract $function The function to make the arguments for.
565
     * @param array $ruleArgs An array of default arguments specifically for the function.
566
     * @param array $rule The entire rule.
567
     * @return array Returns an array in the form `name => defaultValue`.
568
     */
569 57
    private function makeDefaultArgs(\ReflectionFunctionAbstract $function, array $ruleArgs, array $rule = []) {
0 ignored issues
show
Unused Code introduced by
The parameter $rule is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
570 57
        $ruleArgs = array_change_key_case($ruleArgs);
571 57
        $result = [];
572
573 57
        $pos = 0;
574 57
        foreach ($function->getParameters() as $i => $param) {
575 57
            $name = strtolower($param->name);
576
577 57
            if (array_key_exists($name, $ruleArgs)) {
578 3
                $value = $ruleArgs[$name];
579 55
            } elseif ($param->getClass()
580 55
                && !(isset($ruleArgs[$pos]) && is_object($ruleArgs[$pos]) && get_class($ruleArgs[$pos]) === $param->getClass()->name)
581 55
                && ($param->getClass()->isInstantiable() || isset($this->rules[$param->getClass()->name]) || array_key_exists($param->getClass()->name, $this->instances))
582
            ) {
583 10
                $value = new DefaultReference($this->normalizeID($param->getClass()->name));
584 54
            } elseif (array_key_exists($pos, $ruleArgs)) {
585 24
                $value = $ruleArgs[$pos];
586 24
                $pos++;
587 34
            } elseif ($param->isDefaultValueAvailable()) {
588 27
                $value = $param->getDefaultValue();
589
            } else {
590 7
                $value = new RequiredParameter($param);
591
            }
592
593 57
            $result[$name] = $value;
594
        }
595
596 57
        return $result;
597
    }
598
599
    /**
600
     * Replace an array of default args with called args.
601
     *
602
     * @param array $defaultArgs The default arguments from {@link Container::makeDefaultArgs()}.
603
     * @param array $args The arguments passed into a creation.
604
     * @param mixed $instance An object instance if the arguments are being resolved on an already constructed object.
605
     * @return array Returns an array suitable to be applied to a function call.
606
     * @throws MissingArgumentException Throws an exception when a required parameter is missing.
607
     */
608 57
    private function resolveArgs(array $defaultArgs, array $args, $instance = null) {
609
        // First resolve all passed arguments so their types are known.
610 57
        $args = array_map(
611 57
            function ($arg) use ($instance) {
612 15
                return $arg instanceof ReferenceInterface ? $arg->resolve($this, $instance) : $arg;
613 57
            },
614
            array_change_key_case($args)
615
        );
616
617 57
        $pos = 0;
618 57
        foreach ($defaultArgs as $name => &$default) {
619 57
            if (array_key_exists($name, $args)) {
620
                // This is a named arg and should be used.
621 2
                $value = $args[$name];
622 57
            } elseif (isset($args[$pos]) && (!($default instanceof DefaultReference) || empty($default->getClass()) || is_a($args[$pos], $default->getClass()))) {
623
                // There is an arg at this position and it's the same type as the default arg or the default arg is typeless.
624 13
                $value = $args[$pos];
625 13
                $pos++;
626
            } else {
627
                // There is no passed arg, so use the default arg.
628 46
                $value = $default;
629
            }
630
631 57
            if ($value instanceof ReferenceInterface) {
632 14
                $value = $value->resolve($this, $instance);
633
            }
634
635 55
            $default = $value;
636
        }
637
638 55
        return $defaultArgs;
639
    }
640
641
    /**
642
     * Create an instance of a container item.
643
     *
644
     * This method either creates a new instance or returns an already created shared instance.
645
     *
646
     * @param string $nid The normalized ID of the container item.
647
     * @param array $args Additional arguments to pass to the constructor.
648
     * @return object Returns an object instance.
649
     */
650 64
    private function createInstance($nid, array $args) {
651 64
        $rule = $this->makeRule($nid);
652
653
        // Cache the instance or its factory for future use.
654 64
        if (empty($rule['shared'])) {
655 42
            $factory = $this->makeFactory($nid, $rule);
656 41
            $instance = $factory($args);
657 40
            $this->factories[$nid] = $factory;
658
        } else {
659 23
            $instance = $this->createSharedInstance($nid, $rule, $args);
660
        }
661 60
        return $instance;
662
    }
663
664
    /**
665
     * Call a callback with argument injection.
666
     *
667
     * @param callable $callback The callback to call.
668
     * @param array $args Additional arguments to pass to the callback.
669
     * @return mixed Returns the result of the callback.
670
     * @throws ContainerException Throws an exception if the callback cannot be understood.
671
     */
672 3
    public function call(callable $callback, array $args = []) {
673 3
        $instance = null;
674
675 3
        if (is_array($callback)) {
676 1
            $function = new \ReflectionMethod($callback[0], $callback[1]);
677
678 1
            if (is_object($callback[0])) {
679 1
                $instance = $callback[0];
680
            }
681
        } else {
682 2
            $function = new \ReflectionFunction($callback);
683
        }
684
685 3
        $args = $this->resolveArgs($this->makeDefaultArgs($function, $args), [], $instance);
686
687 3
        return call_user_func_array($callback, $args);
688
    }
689
690
    /**
691
     * Returns true if the container can return an entry for the given identifier. Returns false otherwise.
692
     *
693
     * @param string $id Identifier of the entry to look for.
694
     *
695
     * @return boolean
696
     */
697 5
    public function has($id) {
698 5
        $id = $this->normalizeID($id);
699
700 5
        return isset($this->instances[$id]) || !empty($this->rules[$id]) || class_exists($id);
701
    }
702
703
    /**
704
     * Determines whether a rule has been defined at a given ID.
705
     *
706
     * @param string $id Identifier of the entry to look for.
707
     * @return bool Returns **true** if a rule has been defined or **false** otherwise.
708
     */
709 4
    public function hasRule($id) {
710 4
        $id = $this->normalizeID($id);
711 4
        return !empty($this->rules[$id]);
712
    }
713
714
    /**
715
     * Finds an entry of the container by its identifier and returns it.
716
     *
717
     * @param string $id Identifier of the entry to look for.
718
     *
719
     * @throws NotFoundException  No entry was found for this identifier.
720
     * @throws ContainerException Error while retrieving the entry.
721
     *
722
     * @return mixed Entry.
723
     */
724 56
    public function get($id) {
725 56
        return $this->getArgs($id);
726
    }
727
728
    /**
729
     * Determine the reflection information for a callback.
730
     *
731
     * @param callable $callback The callback to reflect.
732
     * @return \ReflectionFunctionAbstract Returns the reflection function for the callback.
733
     */
734 9
    private function reflectCallback(callable $callback) {
735 9
        if (is_array($callback)) {
736 2
            return new \ReflectionMethod($callback[0], $callback[1]);
737
        } else {
738 7
            return new \ReflectionFunction($callback);
739
        }
740
    }
741
}
742