EventDispatcher::injectDispatcherParameters()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 11
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 7
nc 2
nop 2
1
<?php
2
3
/**
4
 * @copyright 2018 Aleksander Stelmaczonek <[email protected]>
5
 * @license   MIT License, see license file distributed with this source code
6
 */
7
8
namespace Koriit\EventDispatcher;
9
10
use Invoker\Exception\InvocationException;
11
use Invoker\Exception\NotCallableException;
12
use Invoker\Exception\NotEnoughParametersException;
13
use Invoker\InvokerInterface;
14
use Koriit\EventDispatcher\Exceptions\InvalidPriority;
15
use Koriit\EventDispatcher\Exceptions\OverriddenParameter;
16
17
class EventDispatcher implements EventDispatcherInterface
18
{
19
    /**
20
     * @var array
21
     */
22
    protected $listeners = [];
23
24
    /**
25
     * @var InvokerInterface
26
     */
27
    protected $invoker;
28
29
    /**
30
     * Indicates whether listeners array can be assumed to be sorted.
31
     *
32
     * @var bool
33
     */
34
    protected $listenersSorted = true;
35
36
    public function __construct(InvokerInterface $invoker)
37
    {
38
        $this->invoker = $invoker;
39
    }
40
41
    public function dispatch($eventName, $parameters = [])
42
    {
43
        $eventContext = new EventContext($eventName);
44
        $this->injectDispatcherParameters($eventContext, $parameters);
45
46
        if (isset($this->listeners[$eventName])) {
47
            $this->sortListenersByPriority();
48
49
            foreach ($this->listeners[$eventName] as $listenersByPriority) {
50
                foreach ($listenersByPriority as $listener) {
51
                    $this->invokeListener($eventContext, $listener, $parameters);
52
                }
53
            }
54
        }
55
56
        return $eventContext;
57
    }
58
59
    public function addListener($eventName, $listener, $priority = 0)
60
    {
61
        $this->validatePriority($priority);
62
63
        $this->listeners[$eventName][$priority][] = $listener;
64
        $this->listenersSorted = false;
65
    }
66
67
    public function addListeners($listeners)
68
    {
69
        foreach ($listeners as $eventName => $eventListeners) {
70
            foreach ($eventListeners as $priority => $listenersByPriority) {
71
                $this->validatePriority($priority);
72
73
                foreach ($listenersByPriority as $listener) {
74
                    $this->listeners[$eventName][$priority][] = $listener;
75
                }
76
            }
77
        }
78
79
        $this->listenersSorted = false;
80
    }
81
82
    public function getListeners($eventName)
83
    {
84
        return isset($this->listeners[$eventName]) ? $this->listeners[$eventName] : [];
85
    }
86
87
    public function getAllListeners()
88
    {
89
        return $this->listeners;
90
    }
91
92
    public function removeListener($eventName, $listener)
93
    {
94
        if (!isset($this->listeners[$eventName])) {
95
            return;
96
        }
97
98
        foreach ($this->listeners[$eventName] as $priority => $listeners) {
99
            $key = array_search($listener, $listeners, true);
100
            if ($key !== false) {
101
                unset($this->listeners[$eventName][$priority][$key]);
102
                $this->removeEmptyItem($this->listeners[$eventName], $priority);
103
            }
104
        }
105
106
        $this->removeEmptyItem($this->listeners, $eventName);
107
    }
108
109
    public function hasListeners($eventName = null)
110
    {
111
        return $eventName !== null ? !empty($this->listeners[$eventName]) : !empty($this->listeners);
112
    }
113
114
    /**
115
     * Sorts internal listeners array by priority.
116
     *
117
     * @return void
118
     */
119
    protected function sortListenersByPriority()
120
    {
121
        if ($this->listenersSorted) {
122
            return;
123
        }
124
125
        foreach (array_keys($this->listeners) as $eventName) {
126
            ksort($this->listeners[$eventName]);
127
        }
128
129
        $this->listenersSorted = true;
130
    }
131
132
    /**
133
     * @param EventContext $eventContext
134
     * @param callable     $listener
135
     * @param array        $parameters
136
     *
137
     * @throws InvocationException
138
     * @throws NotCallableException
139
     * @throws NotEnoughParametersException
140
     *
141
     * @return void
142
     */
143
    protected function invokeListener($eventContext, $listener, $parameters = [])
144
    {
145
        if ($eventContext->isStopped()) {
146
            $eventContext->addStoppedListener($listener);
147
148
            return;
149
        }
150
151
        $eventContext->ignoreReturnValue(false);
152
        $returnValue = $this->invoker->call($listener, $parameters);
153
154
        if ($eventContext->isStopped()) {
155
            $eventContext->setStopValue(true);
156
        } elseif ($this->isStopValue($eventContext, $returnValue)) {
157
            $eventContext->setStopped(true);
158
            $eventContext->setStopValue($returnValue);
159
        }
160
161
        $eventContext->addExecutedListener($listener);
162
    }
163
164
    /**
165
     * Injects predefined dispatcher objects into parameters array.
166
     *
167
     * @param EventContext $eventContext
168
     * @param array        $parameters
169
     *
170
     * @throws OverriddenParameter
171
     *
172
     * @return void
173
     */
174
    protected function injectDispatcherParameters($eventContext, &$parameters)
175
    {
176
        $intersect = \array_intersect_key(\array_flip(['eventName', 'eventContext', 'eventDispatcher']), $parameters);
177
        if (!empty($intersect)) {
178
            throw new OverriddenParameter('Parameters array cannot contain: ' . implode(', ', \array_flip($intersect)));
179
        }
180
181
        $parameters['eventName'] = $eventContext->getEventName();
182
        $parameters['eventContext'] = $eventContext;
183
        $parameters['eventDispatcher'] = $this;
184
    }
185
186
    /**
187
     * @param mixed $priority
188
     *
189
     * @throws InvalidPriority
190
     *
191
     * @return void
192
     */
193
    protected function validatePriority($priority)
194
    {
195
        if (!is_int($priority) || $priority < 0) {
196
            throw new InvalidPriority('Expected non-negative integer priority. Provided: ' . $priority);
197
        }
198
    }
199
200
    /**
201
     * @param EventContext $eventContext
202
     * @param mixed        $returnValue
203
     *
204
     * @return bool
205
     */
206
    protected function isStopValue($eventContext, $returnValue)
207
    {
208
        return !$eventContext->isReturnValueIgnored() && $returnValue;
209
    }
210
211
    /**
212
     * Removes an item by key from the array if it is considered to be empty.
213
     *
214
     * @param array $array
215
     * @param mixed $key
216
     */
217
    protected function removeEmptyItem(&$array, $key)
218
    {
219
        if (empty($array[$key])) {
220
            unset($array[$key]);
221
        }
222
    }
223
}
224