Passed
Push — master ( 37a78a...8b0d30 )
by Alexander
29:44
created

src/Provider/ListenerCollection.php (1 issue)

Labels
Severity
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
     * Get listeners for event class names specified.
21
     *
22
     * @param string ...$eventClassNames Event class names.
23
     *
24
     * @return iterable<callable> Listeners.
25
     */
26 7
    public function getForEvents(string ...$eventClassNames): iterable
27
    {
28 7
        foreach ($eventClassNames as $eventClassName) {
29 7
            if (isset($this->listeners[$eventClassName])) {
30 7
                yield from $this->listeners[$eventClassName];
31
            }
32
        }
33 7
    }
34
35
    /**
36
     * Attaches listener to corresponding event based on the type-hint used for the event argument.
37
     *
38
     * Method signature should be the following:
39
     *
40
     * ```
41
     *  function (MyEvent $event): void
42
     * ```
43
     *
44
     * Any callable could be used be it a closure, invokable object or array referencing a class or object.
45
     *
46
     * @param callable $listener
47
     * @param string $eventClassName
48
     *
49
     * @return self
50
     */
51 8
    public function add(callable $listener, string $eventClassName = ''): self
52
    {
53 8
        $new = clone $this;
54
55 8
        if ($eventClassName === '') {
56 7
            $eventClassName = $this->getParameterType($listener);
57
        }
58
59 7
        $new->listeners[$eventClassName][] = $listener;
60 7
        return $new;
61
    }
62
63
    /**
64
     * Derives the interface type of the first argument of a callable.
65
     *
66
     * @suppress PhanUndeclaredMethod
67
     *
68
     * @param callable $callable The callable for which we want the parameter type.
69
     *
70
     * @return string The interface the parameter is type hinted on.
71
     */
72 7
    private function getParameterType(callable $callable): string
73
    {
74
        // This try-catch is only here to keep OCD linters happy about uncaught reflection exceptions.
75
        try {
76 7
            $closure = new \ReflectionFunction(\Closure::fromCallable($callable));
77 7
            $params = $closure->getParameters();
78
79 7
            $reflectedType = isset($params[0]) ? $params[0]->getType() : null;
80 7
            if ($reflectedType === null) {
81 1
                throw new \InvalidArgumentException('Listeners must declare an object type they can accept.');
82
            }
83 6
            $type = $reflectedType->getName();
0 ignored issues
show
The method getName() does not exist on ReflectionType. It seems like you code against a sub-type of ReflectionType such as ReflectionNamedType. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

83
            /** @scrutinizer ignore-call */ 
84
            $type = $reflectedType->getName();
Loading history...
84 1
        } catch (\ReflectionException $e) {
85
            throw new \RuntimeException('Type error registering listener.', 0, $e);
86
        }
87
88 6
        return $type;
89
    }
90
}
91