Completed
Push — master ( c4d03c...b40aeb )
by Todd
01:34
created

Container   D

Complexity

Total Complexity 105

Size/Duplication

Total Lines 682
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 99.28%

Importance

Changes 0
Metric Value
wmc 105
lcom 1
cbo 3
dl 0
loc 682
ccs 274
cts 276
cp 0.9928
rs 4.5265
c 0
b 0
f 0

33 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
A normalizeID() 0 3 1
A defaultRule() 0 3 1
A rule() 0 11 2
A getClass() 0 3 2
A setClass() 0 4 1
A getAliasOf() 0 3 2
A setAliasOf() 0 10 2
A addAlias() 0 10 2
A removeAlias() 0 10 3
A getAliases() 0 11 4
A getFactory() 0 3 2
A setFactory() 0 4 1
A isShared() 0 3 1
A setShared() 0 4 1
A getInherit() 0 3 1
A setInherit() 0 4 1
A getConstructorArgs() 0 3 2
A setConstructorArgs() 0 4 1
A setInstance() 0 4 1
A addCall() 0 5 1
B getArgs() 0 22 4
D makeRule() 0 46 17
C makeFactory() 0 70 12
C createSharedInstance() 0 63 11
D makeDefaultArgs() 0 26 9
C resolveArgs() 0 25 7
A createInstance() 0 13 2
A call() 0 17 3
A has() 0 5 3
A hasRule() 0 4 1
A get() 0 3 1
A reflectCallback() 0 7 2

How to fix   Complexity   

Complex Class

Complex classes like Container often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Container, and based on these observations, apply Extract Interface, too.

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