ListenerConfigurationChecker   A
last analyzed

Complexity

Total Complexity 23

Size/Duplication

Total Lines 109
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 47
c 3
b 0
f 0
dl 0
loc 109
ccs 60
cts 60
cp 1
rs 10
wmc 23

5 Methods

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

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