Completed
Push — master ( 1ce467...839169 )
by Todd
10s
created

Container::getAliasOf()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

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