Passed
Push — master ( b2d56e...e28abf )
by Alexander
02:11
created

src/ListenerConfigurationChecker.php (1 issue)

Labels
Severity
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Event;
6
7
use Psr\Container\ContainerExceptionInterface;
8
9
use function get_class;
10
use function gettype;
11
use function is_array;
12
use function is_object;
13
use function is_string;
14
15
/**
16
 * ListenerConfigurationChecker could be used in development mode to check if listeners are defined correctly.
17
 *
18
 * ```php
19
 * $checker->check($configuration->get('events-web'));
20
 * ```
21
 */
22
final class ListenerConfigurationChecker
23
{
24
    private CallableFactory $callableFactory;
25
26 22
    public function __construct(CallableFactory $callableFactory)
27
    {
28 22
        $this->callableFactory = $callableFactory;
29 22
    }
30
31
    /**
32
     * Checks the given event configuration and throws an exception in some cases:
33
     * - incorrect configuration format
34
     * - incorrect listener format
35
     * - listener is not a callable
36
     * - listener is meant to be a method of an object which can't be instantiated
37
     *
38
     * @param array $configuration An array in format of [eventClassName => [listeners]]
39
     */
40 22
    public function check(array $configuration): void
41
    {
42 22
        foreach ($configuration as $eventName => $listeners) {
43 22
            if (!is_string($eventName) || !class_exists($eventName)) {
44 1
                throw new InvalidEventConfigurationFormatException(
45
                    'Incorrect event listener format. Format with event name must be used. Got ' .
46 1
                    var_export($eventName, true) . '.'
47
                );
48
            }
49
50 21
            if (!is_iterable($listeners)) {
51 1
                $type = is_object($listeners) ? get_class($listeners) : gettype($listeners);
52
53 1
                throw new InvalidEventConfigurationFormatException(
54 1
                    "Event listeners for $eventName must be an iterable, $type given."
55
                );
56
            }
57
58
            /** @var mixed */
59 20
            foreach ($listeners as $listener) {
60
                try {
61 20
                    if (!$this->isCallable($listener)) {
62 12
                        throw new InvalidListenerConfigurationException(
63 19
                            $this->createNotCallableMessage($listener)
64
                        );
65
                    }
66 13
                } catch (ContainerExceptionInterface $exception) {
67 1
                    throw new InvalidListenerConfigurationException(
68 1
                        'Could not instantiate event listener or listener class has invalid configuration. Got ' .
69 1
                        $this->listenerDump($listener) . '.',
70 1
                        0,
71
                        $exception
72
                    );
73
                }
74
            }
75
        }
76 7
    }
77
78
    /**
79
     * @param mixed $definition
80
     */
81 12
    private function createNotCallableMessage($definition): string
82
    {
83 12
        if (is_string($definition) && class_exists($definition)) {
84 2
            if (!method_exists($definition, '__invoke')) {
85 1
                return sprintf(
86 1
                    '"__invoke" method is not defined in "%s" class.',
87
                    $definition
88
                );
89
            }
90
91 1
            return sprintf(
92 1
                'Failed to instantiate "%s" class.',
93
                $definition
0 ignored issues
show
$definition of type object is incompatible with the type double|integer|string expected by parameter $values of sprintf(). ( Ignorable by Annotation )

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

93
                /** @scrutinizer ignore-type */ $definition
Loading history...
94
            );
95
        }
96
97 10
        if (is_array($definition)
98 10
            && array_keys($definition) === [0, 1]
99 10
            && is_string($definition[1])
100
        ) {
101 6
            if (is_string($definition[0]) && class_exists($definition[0])) {
102 2
                return sprintf(
103 2
                    'Could not instantiate "%s" or "%s" method is not defined in this class.',
104 2
                    $definition[0],
105 2
                    $definition[1],
106
                );
107
            }
108 4
            if (is_object($definition[0])) {
109 1
                return sprintf(
110 1
                    '"%s" method is not defined in "%s" class.',
111 1
                    $definition[1],
112 1
                    get_class($definition[0]),
113
                );
114
            }
115
        }
116
117 7
        return 'Listener must be a callable. Got ' . $this->listenerDump($definition) . '.';
118
    }
119
120
    /**
121
     * @param mixed $definition
122
     *
123
     * @throws ContainerExceptionInterface Error while retrieving the entry from container.
124
     */
125 20
    private function isCallable($definition): bool
126
    {
127
        try {
128 20
            $this->callableFactory->create($definition);
129 13
        } catch (InvalidListenerConfigurationException $e) {
130 12
            return false;
131
        }
132
133 7
        return true;
134
    }
135
136
    /**
137
     * @param mixed $listener
138
     */
139 8
    private function listenerDump($listener): string
140
    {
141 8
        return is_object($listener) ? get_class($listener) : var_export($listener, true);
142
    }
143
}
144