Completed
Push — dev ( ba12db...041f79 )
by Aleksander
01:58
created

EventDispatcher::removeEmptyItem()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 6
rs 9.4285
cc 2
eloc 3
nc 2
nop 2
1
<?php
2
3
/**
4
 * @copyright 2017 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 DI\InvokerInterface;
11
use Koriit\EventDispatcher\Exceptions\InvalidPriority;
12
use Koriit\EventDispatcher\Exceptions\OverriddenParameter;
13
14
/**
15
 * @author Aleksander Stelmaczonek <[email protected]>
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
     * @return void
138
     */
139
    protected function invokeListener($eventContext, $listener, $parameters = [])
140
    {
141
        if ($eventContext->isStopped()) {
142
            $eventContext->addStoppedListener($listener);
143
            return;
144
        }
145
146
        $eventContext->ignoreReturnValue(false);
147
        $returnValue = $this->invoker->call($listener, $parameters);
148
149
        if ($eventContext->isStopped()) {
150
            $eventContext->setStopValue(true);
151
152
        } else if ($this->isStopValue($eventContext, $returnValue)) {
153
            $eventContext->setStopped(true);
154
            $eventContext->setStopValue($returnValue);
155
        }
156
157
        $eventContext->addExecutedListener($listener);
158
    }
159
160
    /**
161
     * Injects predefined disptacher objects into parameters array.
162
     *
163
     * @param EventContext $eventContext
164
     * @param array $parameters
165
     *
166
     * @throws OverriddenParameter
167
     *
168
     * @return void
169
     */
170
    protected function injectDispatcherParameters($eventContext, &$parameters)
171
    {
172
        if (\array_intersect_key($parameters, \array_flip(['eventName', 'eventContext', 'eventDispatcher']))) {
173
            throw new OverriddenParameter('Following parameters cannot be passed in parameters array: eventName, eventContext, eventDispatcher.');
174
        }
175
176
        $parameters['eventName'] = $eventContext->getEventName();
177
        $parameters['eventContext'] = $eventContext;
178
        $parameters['eventDispatcher'] = $this;
179
    }
180
181
    /**
182
     * @param mixed $priority
183
     *
184
     * @throws InvalidPriority
185
     *
186
     * @return void
187
     */
188
    protected function validatePriority($priority)
189
    {
190
        if (!is_int($priority) || $priority < 0) {
191
            throw new InvalidPriority('Expected non-negative integer priority. Provided: ' . $priority);
192
        }
193
    }
194
195
    /**
196
     * @param EventContext $eventContext
197
     * @param mixed $returnValue
198
     *
199
     * @return bool
200
     */
201
    protected function isStopValue($eventContext, $returnValue)
202
    {
203
        return !$eventContext->isReturnValueIgnored() && $returnValue;
204
    }
205
206
    /**
207
     * Removes an item by key from the array if it is considered to be empty.
208
     *
209
     * @param array $array
210
     * @param mixed $key
211
     */
212
    protected function removeEmptyItem(&$array, $key)
213
    {
214
        if (empty($array[$key])) {
215
            unset($array[$key]);
216
        }
217
    }
218
}
219