Passed
Push — master ( c93144...886a37 )
by Alexander
01:14
created

ListenerCollection::isObjectCallable()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 1
c 1
b 0
f 0
dl 0
loc 3
ccs 0
cts 2
cp 0
rs 10
cc 2
nc 2
nop 1
crap 6
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\EventDispatcher\Provider;
6
7
/**
8
 * Listener collection stores listeners and is used to configure provider.
9
 *
10
 * @see Provider
11
 */
12
final class ListenerCollection
13
{
14
    /**
15
     * @var callable[]
16
     */
17
    private array $listeners = [];
18
19
    /**
20
     * @param string ...$eventClassNames
21
     * @return iterable<callable>
22
     */
23 7
    public function getForEvents(string ...$eventClassNames): iterable
24
    {
25 7
        foreach ($eventClassNames as $eventClassName) {
26 7
            if (isset($this->listeners[$eventClassName])) {
27 7
                yield from $this->listeners[$eventClassName];
28
            }
29
        }
30 7
    }
31
32
    /**
33
     * Attaches listener to corresponding event based on the type-hint used for the event argument.
34
     *
35
     * Method signature should be the following:
36
     *
37
     * ```
38
     *  function (MyEvent $event): void
39
     * ```
40
     *
41
     * Any callable could be used be it a closure, invokable object or array referencing a class or object.
42
     *
43
     * @param callable $listener
44
     * @param string $eventClassName
45
     * @return self
46
     */
47 8
    public function add(callable $listener, string $eventClassName = ''): self
48
    {
49 8
        $new = clone $this;
50
51 8
        if ($eventClassName === '') {
52 7
            $eventClassName = $this->getParameterType($listener);
53
        }
54
55 7
        $new->listeners[$eventClassName][] = $listener;
56 7
        return $new;
57
    }
58
59
    /**
60
     * Derives the interface type of the first argument of a callable.
61
     *
62
     * @suppress PhanUndeclaredMethod
63
     *
64
     * @param callable $callable The callable for which we want the parameter type.
65
     * @return string The interface the parameter is type hinted on.
66
     */
67 7
    private function getParameterType(callable $callable): string
68
    {
69
        // This try-catch is only here to keep OCD linters happy about uncaught reflection exceptions.
70
        try {
71 7
            $closure = new \ReflectionFunction(\Closure::fromCallable($callable));
72 7
            $params = $closure->getParameters();
73
74 7
            $reflectedType = isset($params[0]) ? $params[0]->getType() : null;
75 7
            if ($reflectedType === null) {
76 1
                throw new \InvalidArgumentException('Listeners must declare an object type they can accept.');
77
            }
78 6
            $type = $reflectedType->getName();
79 1
        } catch (\ReflectionException $e) {
80
            throw new \RuntimeException('Type error registering listener.', 0, $e);
81
        }
82
83 6
        return $type;
84
    }
85
}
86