Completed
Push — master ( 7376bc...11f4d7 )
by Todd
01:45
created

Container::arrayClone()   A

Complexity

Conditions 3
Paths 1

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3.0987

Importance

Changes 0
Metric Value
dl 0
loc 11
ccs 7
cts 9
cp 0.7778
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 8
nc 1
nop 1
crap 3.0987
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 73
    public function __construct() {
26 73
        $this->rules = ['*' => ['inherit' => true, 'constructorArgs' => null]];
27 73
        $this->instances = [];
28 73
        $this->factories = [];
29
30 73
        $this->rule('*');
31 73
    }
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 1
                    ? clone $element
54
                    : $element
55
                )
56 1
            );
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 74
    private function normalizeID($id) {
67 74
        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 73
    public function rule($id) {
86 73
        $id = $this->normalizeID($id);
87
88 73
        if (!isset($this->rules[$id])) {
89 35
            $this->rules[$id] = [];
90 35
        }
91 73
        $this->currentRuleName = $id;
92 73
        $this->currentRule = &$this->rules[$id];
93
94 73
        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 1
        } 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.
148
     *
149
     * If {@link Container::addAlias()} is called with an alias that is the same as the current rule then an **E_USER_NOTICE**
150
     * level error is raised and the alias is not added.
151
     *
152
     * @param string $alias The alias to set.
153
     * @return $this
154
     */
155 7
    public function addAlias($alias) {
156 7
        $alias = $this->normalizeID($alias);
157
158 7
        if ($alias === $this->currentRuleName) {
159 1
            trigger_error("Tried to set alias '$alias' to self.", E_USER_NOTICE);
160 1
        } else {
161 6
            $this->rules[$alias]['aliasOf'] = $this->currentRuleName;
162
        }
163 7
        return $this;
164
    }
165
166
    /**
167
     * Remove an alias of the current rule.
168
     *
169
     * If {@link Container::removeAlias()} is called with an alias that references a different rule then an **E_USER_NOTICE**
170
     * level error is raised, but the alias is still removed.
171
     *
172
     * @param string $alias The alias to remove.
173
     * @return $this
174
     */
175 2
    public function removeAlias($alias) {
176 2
        $alias = $this->normalizeID($alias);
177
178 2
        if (!empty($this->rules[$alias]['aliasOf']) && $this->rules[$alias]['aliasOf'] !== $this->currentRuleName) {
179 1
            trigger_error("Alias '$alias' does not point to the current rule.", E_USER_NOTICE);
180 1
        }
181
182 2
        unset($this->rules[$alias]['aliasOf']);
183 2
        return $this;
184
    }
185
186
    /**
187
     * Get all of the aliases of the current rule.
188
     *
189
     * This method is intended to aid in debugging and should not be used in production as it walks the entire rule array.
190
     *
191
     * @return array Returns an array of strings representing aliases.
192
     */
193 6
    public function getAliases() {
194 6
        $result = [];
195
196 6
        foreach ($this->rules as $name => $rule) {
197 6
            if (!empty($rule['aliasOf']) && $rule['aliasOf'] === $this->currentRuleName) {
198 4
                $result[] = $name;
199 4
            }
200 6
        }
201
202 6
        return $result;
203
    }
204
205
    /**
206
     * Get the factory callback for the current rule.
207
     *
208
     * @return callable|null Returns the rule's factory or **null** if it has none.
209
     */
210 2
    public function getFactory() {
211 2
        return isset($this->currentRule['factory']) ? $this->currentRule['factory'] : null;
212
    }
213
214
    /**
215
     * Set the factory that will be used to create the instance for the current rule.
216
     *
217
     * @param callable $factory This callback will be called to create the instance for the rule.
218
     * @return $this
219
     */
220 10
    public function setFactory(callable $factory) {
221 10
        $this->currentRule['factory'] = $factory;
222 10
        return $this;
223
    }
224
225
    /**
226
     * Whether or not the current rule is shared.
227
     *
228
     * @return bool Returns **true** if the rule is shared or **false** otherwise.
229
     */
230 2
    public function isShared() {
231 2
        return !empty($this->currentRule['shared']);
232
    }
233
234
    /**
235
     * Set whether or not the current rule is shared.
236
     *
237
     * @param bool $shared Whether or not the current rule is shared.
238
     * @return $this
239
     */
240 26
    public function setShared($shared) {
241 26
        $this->currentRule['shared'] = $shared;
242 26
        return $this;
243
    }
244
245
    /**
246
     * Whether or not the current rule will inherit to subclasses.
247
     *
248
     * @return bool Returns **true** if the current rule inherits or **false** otherwise.
249
     */
250 2
    public function getInherit() {
251 2
        return !empty($this->currentRule['inherit']);
252
    }
253
254
    /**
255
     * Set whether or not the current rule extends to subclasses.
256
     *
257
     * @param bool $inherit Pass **true** to have subclasses inherit this rule or **false** otherwise.
258
     * @return $this
259
     */
260 3
    public function setInherit($inherit) {
261 3
        $this->currentRule['inherit'] = $inherit;
262 3
        return $this;
263
    }
264
265
    /**
266
     * Get the constructor arguments for the current rule.
267
     *
268
     * @return array Returns the constructor arguments for the current rule.
269
     */
270 2
    public function getConstructorArgs() {
271 2
        return empty($this->currentRule['constructorArgs']) ? [] : $this->currentRule['constructorArgs'];
272
    }
273
274
    /**
275
     * Set the constructor arguments for the current rule.
276
     *
277
     * @param array $args An array of constructor arguments.
278
     * @return $this
279
     */
280 18
    public function setConstructorArgs(array $args) {
281 18
        $this->currentRule['constructorArgs'] = $args;
282 18
        return $this;
283
    }
284
285
    /**
286
     * Set a specific shared instance into the container.
287
     *
288
     * When you set an instance into the container then it will always be returned by subsequent retrievals, even if a
289
     * rule is configured that says that instances should not be shared.
290
     *
291
     * @param string $name The name of the container entry.
292
     * @param mixed $instance This instance.
293
     * @return $this
294
     */
295 7
    public function setInstance($name, $instance) {
296 7
        $this->instances[$this->normalizeID($name)] = $instance;
297 7
        return $this;
298
    }
299
300
    /**
301
     * Add a method call to a rule.
302
     *
303
     * @param string $method The name of the method to call.
304
     * @param array $args The arguments to pass to the method.
305
     * @return $this
306
     */
307 6
    public function addCall($method, array $args = []) {
308 6
        $this->currentRule['calls'][] = [$method, $args];
309
310 6
        return $this;
311
    }
312
313
    /**
314
     * Finds an entry of the container by its identifier and returns it.
315
     *
316
     * @param string $id Identifier of the entry to look for.
317
     * @param array $args Additional arguments to pass to the constructor.
318
     *
319
     * @throws NotFoundException No entry was found for this identifier.
320
     * @throws ContainerException Error while retrieving the entry.
321
     *
322
     * @return mixed Entry.
323
     */
324 54
    public function getArgs($id, array $args = []) {
325 54
        $id = $this->normalizeID($id);
326
327 54
        if (isset($this->instances[$id])) {
328
            // A shared instance just gets returned.
329 14
            return $this->instances[$id];
330
        }
331
332 50
        if (isset($this->factories[$id])) {
333
            // The factory for this object type is already there so call it to create the instance.
334 3
            return $this->factories[$id]($args);
335
        }
336
337 50
        if (!empty($this->rules[$id]['aliasOf'])) {
338
            // This rule references another rule.
339 2
            return $this->getArgs($this->rules[$id]['aliasOf'], $args);
340
        }
341
342
        // The factory or instance isn't registered so do that now.
343
        // This call also caches the instance or factory fo faster access next time.
344 50
        return $this->createInstance($id, $args);
345
    }
346
347
    /**
348
     * Make a rule based on an ID.
349
     *
350
     * @param string $nid A normalized ID.
351
     * @return array Returns an array representing a rule.
352
     */
353 50
    private function makeRule($nid) {
354 50
        $rule = isset($this->rules[$nid]) ? $this->rules[$nid] : [];
355
356 50
        if (class_exists($nid)) {
357 42
            for ($class = get_parent_class($nid); !empty($class); $class = get_parent_class($class)) {
358
                // Don't add the rule if it doesn't say to inherit.
359 6
                if (!isset($this->rules[$class]) || (isset($this->rules[$class]['inherit']) && !$this->rules[$class]['inherit'])) {
360 4
                    break;
361
                }
362 2
                $rule += $this->rules[$class];
363 2
            }
364
365
            // Add the default rule.
366 42
            if (!empty($this->rules['*']['inherit'])) {
367 42
                $rule += $this->rules['*'];
368 42
            }
369
370
            // Add interface calls to the rule.
371 42
            $interfaces = class_implements($nid);
372 42
            foreach ($interfaces as $interface) {
373 35
                if (isset($this->rules[$interface])) {
374 10
                    $interfaceRule = $this->rules[$interface];
375
376 10
                    if (isset($interfaceRule['inherit']) && $interfaceRule['inherit'] === false) {
377 1
                        continue;
378
                    }
379
380 9
                    if (!isset($rule['shared']) && isset($interfaceRule['shared'])) {
381 3
                        $rule['shared'] = $interfaceRule['shared'];
382 3
                    }
383
384 9
                    if (!isset($rule['constructorArgs']) && isset($interfaceRule['constructorArgs'])) {
385 3
                        $rule['constructorArgs'] = $interfaceRule['constructorArgs'];
386 3
                    }
387
388 9
                    if (!empty($interfaceRule['calls'])) {
389 2
                        $rule['calls'] = array_merge(
390 2
                            isset($rule['calls']) ? $rule['calls'] : [],
391 2
                            $interfaceRule['calls']
392 2
                        );
393 2
                    }
394 9
                }
395 42
            }
396 50
        } elseif (!empty($this->rules['*']['inherit'])) {
397
            // Add the default rule.
398 10
            $rule += $this->rules['*'];
399 10
        }
400
401 50
        return $rule;
402
    }
403
404
    /**
405
     * Make a function that creates objects from a rule.
406
     *
407
     * @param string $nid The normalized ID of the container item.
408
     * @param array $rule The resolved rule for the ID.
409
     * @return \Closure Returns a function that when called will create a new instance of the class.
410
     * @throws NotFoundException No entry was found for this identifier.
411
     */
412 34
    private function makeFactory($nid, array $rule) {
413 34
        $className = empty($rule['class']) ? $nid : $rule['class'];
414
415 34
        if (!empty($rule['factory'])) {
416
            // The instance is created with a user-supplied factory function.
417 6
            $callback = $rule['factory'];
418 6
            $function = $this->reflectCallback($callback);
419
420 6
            if ($function->getNumberOfParameters() > 0) {
421 3
                $callbackArgs = $this->makeDefaultArgs($function, (array)$rule['constructorArgs'], $rule);
422
                $factory = function ($args) use ($callback, $callbackArgs) {
423 3
                    return call_user_func_array($callback, $this->resolveArgs($callbackArgs, $args));
424 3
                };
425 3
            } else {
426 3
                $factory = $callback;
427
            }
428
429
            // If a class is specified then still reflect on it so that calls can be made against it.
430 6
            if (class_exists($className)) {
431 1
                $class = new \ReflectionClass($className);
432 1
            }
433 6
        } else {
434
            // The instance is created by newing up a class.
435 28
            if (!class_exists($className)) {
436 1
                throw new NotFoundException("Class $className does not exist.", 404);
437
            }
438 27
            $class = new \ReflectionClass($className);
439 27
            $constructor = $class->getConstructor();
440
441 27
            if ($constructor && $constructor->getNumberOfParameters() > 0) {
442 24
                $constructorArgs = $this->makeDefaultArgs($constructor, (array)$rule['constructorArgs'], $rule);
443
444
                $factory = function ($args) use ($className, $constructorArgs) {
445 24
                    return new $className(...array_values($this->resolveArgs($constructorArgs, $args)));
446 24
                };
447 24
            } else {
448
                $factory = function () use ($className) {
449 3
                    return new $className;
450 3
                };
451
            }
452
        }
453
454
        // Add calls to the factory.
455 33
        if (isset($class) && !empty($rule['calls'])) {
456 4
            $calls = [];
457
458
            // Generate the calls array.
459 4
            foreach ($rule['calls'] as $call) {
460 4
                list($methodName, $args) = $call;
461 4
                $method = $class->getMethod($methodName);
462 4
                $calls[] = [$methodName, $this->makeDefaultArgs($method, $args, $rule)];
463 4
            }
464
465
            // Wrap the factory in one that makes the calls.
466 4
            $factory = function ($args) use ($factory, $calls) {
467 4
                $instance = $factory($args);
468
469 4
                foreach ($calls as $call) {
470 4
                    call_user_func_array(
471 4
                        [$instance, $call[0]],
472 4
                        $this->resolveArgs($call[1], [], $instance)
473 4
                    );
474 4
                }
475
476 4
                return $instance;
477 4
            };
478 4
        }
479
480 33
        return $factory;
481
    }
482
483
    /**
484
     * Create a shared instance of a class from a rule.
485
     *
486
     * This method has the side effect of adding the new instance to the internal instances array of this object.
487
     *
488
     * @param string $nid The normalized ID of the container item.
489
     * @param array $rule The resolved rule for the ID.
490
     * @param array $args Additional arguments passed during creation.
491
     * @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...
492
     * @throws NotFoundException Throws an exception if the class does not exist.
493
     */
494 17
    private function createSharedInstance($nid, array $rule, array $args) {
495 17
        if (!empty($rule['factory'])) {
496
            // The instance is created with a user-supplied factory function.
497 3
            $callback = $rule['factory'];
498 3
            $function = $this->reflectCallback($callback);
499
500 3
            if ($function->getNumberOfParameters() > 0) {
501 1
                $callbackArgs = $this->resolveArgs(
502 1
                    $this->makeDefaultArgs($function, (array)$rule['constructorArgs'], $rule),
503
                    $args
504 1
                );
505
506 1
                $this->instances[$nid] = null; // prevent cyclic dependency from infinite loop.
507 1
                $this->instances[$nid] = $instance = call_user_func_array($callback, $callbackArgs);
508 1
            } else {
509 2
                $this->instances[$nid] = $instance = $callback();
510
            }
511
512
            // Reflect on the instance so that calls can be made against it.
513 3
            if (is_object($instance)) {
514 2
                $class = new \ReflectionClass(get_class($instance));
515 2
            }
516 3
        } else {
517 14
            $className = empty($rule['class']) ? $nid : $rule['class'];
518 14
            if (!class_exists($className)) {
519 1
                throw new NotFoundException("Class $className does not exist.", 404);
520
            }
521 13
            $class = new \ReflectionClass($className);
522 13
            $constructor = $class->getConstructor();
523
524 13
            if ($constructor && $constructor->getNumberOfParameters() > 0) {
525
                // Instantiate the object first so that this instance can be used for cyclic dependencies.
526 12
                $this->instances[$nid] = $instance = $class->newInstanceWithoutConstructor();
527
528 12
                $constructorArgs = $this->resolveArgs(
529 12
                    $this->makeDefaultArgs($constructor, (array)$rule['constructorArgs'], $rule),
530
                    $args
531 12
                );
532 12
                $constructor->invokeArgs($instance, $constructorArgs);
533 12
            } else {
534 1
                $this->instances[$nid] = $instance = new $class->name;
535
            }
536
        }
537
538
        // Call subsequent calls on the new object.
539 16
        if (isset($class) && !empty($rule['calls'])) {
540 2
            foreach ($rule['calls'] as $call) {
541 2
                list($methodName, $args) = $call;
542 2
                $method = $class->getMethod($methodName);
543
544 2
                $args = $this->resolveArgs(
545 2
                    $this->makeDefaultArgs($method, $args, $rule),
546 2
                    [],
547
                    $instance
548 2
                );
549
550 2
                $method->invokeArgs($instance, $args);
551 2
            }
552 2
        }
553
554 16
        return $instance;
555
    }
556
557
    /**
558
     * Make an array of default arguments for a given function.
559
     *
560
     * @param \ReflectionFunctionAbstract $function The function to make the arguments for.
561
     * @param array $ruleArgs An array of default arguments specifically for the function.
562
     * @param array $rule The entire rule.
563
     * @return array Returns an array in the form `name => defaultValue`.
564
     */
565 43
    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...
566 43
        $ruleArgs = array_change_key_case($ruleArgs);
567 43
        $result = [];
568
569 43
        $pos = 0;
570 43
        foreach ($function->getParameters() as $i => $param) {
571 43
            $name = strtolower($param->name);
572
573 43
            if (array_key_exists($name, $ruleArgs)) {
574 2
                $value = $ruleArgs[$name];
575 43
            } elseif ($param->getClass()
576 41
                && !(isset($ruleArgs[$pos]) && is_object($ruleArgs[$pos]) && get_class($ruleArgs[$pos]) === $param->getClass()->getName())
0 ignored issues
show
Bug introduced by
Consider using $param->getClass()->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
577 41
                && ($param->getClass()->isInstantiable() || isset($this->rules[$param->getClass()->getName()]) || array_key_exists($param->getClass()->getName(), $this->instances))
0 ignored issues
show
Bug introduced by
Consider using $param->getClass()->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
578 41
            ) {
579 9
                $value = new DefaultReference($this->normalizeID($param->getClass()->getName()));
0 ignored issues
show
Bug introduced by
Consider using $param->getClass()->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
580 41
            } elseif (array_key_exists($pos, $ruleArgs)) {
581 18
                $value = $ruleArgs[$pos];
582 18
                $pos++;
583 40
            } elseif ($param->isDefaultValueAvailable()) {
584 25
                $value = $param->getDefaultValue();
585 25
            } else {
586 1
                $value = null;
587
            }
588
589 43
            $result[$name] = $value;
590 43
        }
591
592 43
        return $result;
593
    }
594
595
    /**
596
     * Replace an array of default args with called args.
597
     *
598
     * @param array $defaultArgs The default arguments from {@link Container::makeDefaultArgs()}.
599
     * @param array $args The arguments passed into a creation.
600
     * @param mixed $instance An object instance if the arguments are being resolved on an already constructed object.
601
     * @return array Returns an array suitable to be applied to a function call.
602
     */
603 43
    private function resolveArgs(array $defaultArgs, array $args, $instance = null) {
604 43
        $args = array_change_key_case($args);
605
606 43
        $pos = 0;
607 43
        foreach ($defaultArgs as $name => &$arg) {
608 43
            if (array_key_exists($name, $args)) {
609
                // This is a named arg and should be used.
610 2
                $value = $args[$name];
611 43
            } elseif (isset($args[$pos]) && (!($arg instanceof DefaultReference) || is_a($args[$pos], $arg->getName()))) {
612
                // There is an arg at this position and it's the same type as the default arg or the default arg is typeless.
613 4
                $value = $args[$pos];
614 4
                $pos++;
615 4
            } else {
616
                // There is no passed arg, so use the default arg.
617 40
                $value = $arg;
618
            }
619
620 43
            if ($value instanceof ReferenceInterface) {
621 10
                $value = $value->resolve($this, $instance);
622 10
            }
623 43
            $arg = $value;
624 43
        }
625
626 43
        return $defaultArgs;
627
    }
628
629
    /**
630
     * Create an instance of a container item.
631
     *
632
     * This method either creates a new instance or returns an already created shared instance.
633
     *
634
     * @param string $nid The normalized ID of the container item.
635
     * @param array $args Additional arguments to pass to the constructor.
636
     * @return object Returns an object instance.
637
     */
638 50
    private function createInstance($nid, array $args) {
639 50
        $rule = $this->makeRule($nid);
640
641
        // Cache the instance or its factory for future use.
642 50
        if (empty($rule['shared'])) {
643 34
            $factory = $this->makeFactory($nid, $rule);
644 33
            $instance = $factory($args);
645 33
            $this->factories[$nid] = $factory;
646 33
        } else {
647 17
            $instance = $this->createSharedInstance($nid, $rule, $args);
648
        }
649 48
        return $instance;
650
    }
651
652
    /**
653
     * Call a callback with argument injection.
654
     *
655
     * @param callable $callback The callback to call.
656
     * @param array $args Additional arguments to pass to the callback.
657
     * @return mixed Returns the result of the callback.
658
     * @throws ContainerException Throws an exception if the callback cannot be understood.
659
     */
660 3
    public function call(callable $callback, array $args = []) {
661 3
        $instance = null;
662
663 3
        if (is_array($callback)) {
664 1
            $function = new \ReflectionMethod($callback[0], $callback[1]);
665
666 1
            if (is_object($callback[0])) {
667 1
                $instance = $callback[0];
668 1
            }
669 1
        } else {
670 2
            $function = new \ReflectionFunction($callback);
671
        }
672
673 3
        $args = $this->resolveArgs($this->makeDefaultArgs($function, $args), [], $instance);
674
675 3
        return call_user_func_array($callback, $args);
676
    }
677
678
    /**
679
     * Returns true if the container can return an entry for the given identifier. Returns false otherwise.
680
     *
681
     * @param string $id Identifier of the entry to look for.
682
     *
683
     * @return boolean
684
     */
685 5
    public function has($id) {
686 5
        $id = $this->normalizeID($id);
687
688 5
        return isset($this->instances[$id]) || !empty($this->rules[$id]) || class_exists($id);
689
    }
690
691
    /**
692
     * Determines whether a rule has been defined at a given ID.
693
     *
694
     * @param string $id Identifier of the entry to look for.
695
     * @return bool Returns **true** if a rule has been defined or **false** otherwise.
696
     */
697 4
    public function hasRule($id) {
698 4
        $id = $this->normalizeID($id);
699 4
        return !empty($this->rules[$id]);
700
    }
701
702
    /**
703
     * Finds an entry of the container by its identifier and returns it.
704
     *
705
     * @param string $id Identifier of the entry to look for.
706
     *
707
     * @throws NotFoundException  No entry was found for this identifier.
708
     * @throws ContainerException Error while retrieving the entry.
709
     *
710
     * @return mixed Entry.
711
     */
712 49
    public function get($id) {
713 49
        return $this->getArgs($id);
714
    }
715
716
    /**
717
     * Determine the reflection information for a callback.
718
     *
719
     * @param callable $callback The callback to reflect.
720
     * @return \ReflectionFunctionAbstract Returns the reflection function for the callback.
721
     */
722 9
    private function reflectCallback(callable $callback) {
723 9
        if (is_array($callback)) {
724 2
            return new \ReflectionMethod($callback[0], $callback[1]);
725
        } else {
726 7
            return new \ReflectionFunction($callback);
727
        }
728
    }
729
}
730