Test Setup Failed
Push — master ( bec311...e89aae )
by Alexander
03:49
created

ListenerConfigurationChecker   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 93
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 40
c 1
b 0
f 0
dl 0
loc 93
rs 10
wmc 22

3 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
B isCallable() 0 36 11
B check() 0 31 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Event;
6
7
use Psr\Container\ContainerExceptionInterface;
8
use Psr\Container\ContainerInterface;
9
use ReflectionException;
10
use ReflectionMethod;
11
use function is_array;
12
use function is_callable;
13
use function is_string;
14
15
final class ListenerConfigurationChecker
16
{
17
    private ContainerInterface $container;
18
19
    public function __construct(ContainerInterface $container)
20
    {
21
        $this->container = $container;
22
    }
23
24
    /**
25
     * Checks the given event configuration and throws an exception in some cases:
26
     * - incorrect configuration format
27
     * - incorrect listener format
28
     * - listener is not a callable
29
     * - listener is meant to be a method of an object which can't be instantiated
30
     *
31
     * @param array $configuration An array in format of [eventClassName => [listeners]]
32
     *
33
     * @psalm-suppress InvalidCatch
34
     */
35
    public function check(array $configuration): void
36
    {
37
        foreach ($configuration as $eventName => $listeners) {
38
            if (!is_string($eventName) || !class_exists($eventName)) {
39
                throw new InvalidEventConfigurationFormatException(
40
                    'Incorrect event listener format. Format with event name must be used.'
41
                );
42
            }
43
44
            if (!is_iterable($listeners)) {
45
                $type = is_object($listeners) ? get_class($listeners) : gettype($listeners);
46
47
                throw new InvalidEventConfigurationFormatException(
48
                    "Event listeners for $eventName must be an iterable, $type given."
49
                );
50
            }
51
52
            foreach ($listeners as $listener) {
53
                try {
54
                    if (!$this->isCallable($listener)) {
55
                        $type = is_object($listener) ? get_class($listener) : gettype($listener);
56
57
                        throw new InvalidListenerConfigurationException(
58
                            "Listener must be a callable, $type given."
59
                        );
60
                    }
61
                } catch (ContainerExceptionInterface $exception) {
62
                    throw new InvalidListenerConfigurationException(
63
                        'Could not instantiate event listener or listener class has invalid configuration.',
64
                        0,
65
                        $exception
66
                    );
67
                }
68
            }
69
        }
70
    }
71
72
    private function isCallable($definition): bool
73
    {
74
        if (
75
            is_array($definition)
76
            && array_keys($definition) === [0, 1]
77
            && is_string($definition[0])
78
        ) {
79
            if (class_exists($definition[0])) {
80
                try {
81
                    $method = new ReflectionMethod($definition[0], $definition[1]);
82
                    if ($method->isStatic()) {
83
                        return true;
84
                    }
85
                } catch (ReflectionException $exception) {
86
                    return false;
87
                }
88
            }
89
90
            if ($this->container->has($definition[0])) {
91
                $object = $this->container->get($definition[0]);
92
93
                return method_exists($object, $definition[1]);
94
            }
95
96
            return false;
97
        }
98
99
        if (is_callable($definition)) {
100
            return true;
101
        }
102
103
        if (is_string($definition) && $this->container->has($definition)) {
104
            return is_callable($this->container->get($definition));
105
        }
106
107
        return false;
108
    }
109
}
110