Passed
Push — master ( 463537...99d689 )
by Alexander
31:02 queued 23:21
created

createNotCallableMessage()   B

Complexity

Conditions 7
Paths 4

Size

Total Lines 22
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 7

Importance

Changes 0
Metric Value
cc 7
eloc 14
nc 4
nop 1
dl 0
loc 22
ccs 15
cts 15
cp 1
crap 7
rs 8.8333
c 0
b 0
f 0
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 20
    public function __construct(CallableFactory $callableFactory)
27
    {
28 20
        $this->callableFactory = $callableFactory;
29 20
    }
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 20
    public function check(array $configuration): void
41
    {
42 20
        foreach ($configuration as $eventName => $listeners) {
43 20
            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 19
            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 18
            foreach ($listeners as $listener) {
60
                try {
61 18
                    if (!$this->isCallable($listener)) {
62 10
                        throw new InvalidListenerConfigurationException(
63 17
                            $this->createNotCallableMessage($listener)
64
                        );
65
                    }
66 11
                } 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 10
    private function createNotCallableMessage($definition): string
82
    {
83 10
        if (is_array($definition)
84 10
            && array_keys($definition) === [0, 1]
85 10
            && is_string($definition[1])
86
        ) {
87 6
            if (is_string($definition[0]) && class_exists($definition[0])) {
88 2
                return sprintf(
89 2
                    'Could not instantiate "%s" or "%s" method is not defined in this class.',
90 2
                    $definition[0],
91 2
                    $definition[1],
92
                );
93
            }
94 4
            if (is_object($definition[0])) {
95 1
                return sprintf(
96 1
                    '"%s" method is not defined in "%s" class.',
97 1
                    $definition[1],
98 1
                    get_class($definition[0]),
99
                );
100
            }
101
        }
102 7
        return 'Listener must be a callable. Got ' . $this->listenerDump($definition) . '.';
103
    }
104
105
    /**
106
     * @param mixed $definition
107
     *
108
     * @throws ContainerExceptionInterface Error while retrieving the entry from container.
109
     */
110 18
    private function isCallable($definition): bool
111
    {
112
        try {
113 18
            $this->callableFactory->create($definition);
114 11
        } catch (InvalidListenerConfigurationException $e) {
115 10
            return false;
116
        }
117
118 7
        return true;
119
    }
120
121
    /**
122
     * @param mixed $listener
123
     */
124 8
    private function listenerDump($listener): string
125
    {
126 8
        return is_object($listener) ? get_class($listener) : var_export($listener, true);
127
    }
128
}
129