Completed
Push — master ( 2b4c9b...369218 )
by Aleksander
05:32 queued 04:26
created

EventDispatcher::isStopValue()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
rs 10
cc 2
eloc 2
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
144
            return;
145
        }
146
147
        $eventContext->ignoreReturnValue(false);
148
        $returnValue = $this->invoker->call($listener, $parameters);
149
150
        if ($eventContext->isStopped()) {
151
            $eventContext->setStopValue(true);
152
        } elseif ($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
        $intersect = \array_intersect_key(\array_flip(['eventName', 'eventContext', 'eventDispatcher']), $parameters);
173
        if (!empty($intersect)) {
174
            throw new OverriddenParameter('Parameters array cannot contain: ' . implode(', ', \array_flip($intersect)));
175
        }
176
177
        $parameters['eventName'] = $eventContext->getEventName();
178
        $parameters['eventContext'] = $eventContext;
179
        $parameters['eventDispatcher'] = $this;
180
    }
181
182
    /**
183
     * @param mixed $priority
184
     *
185
     * @throws InvalidPriority
186
     *
187
     * @return void
188
     */
189
    protected function validatePriority($priority)
190
    {
191
        if (!is_int($priority) || $priority < 0) {
192
            throw new InvalidPriority('Expected non-negative integer priority. Provided: ' . $priority);
193
        }
194
    }
195
196
    /**
197
     * @param EventContext $eventContext
198
     * @param mixed        $returnValue
199
     *
200
     * @return bool
201
     */
202
    protected function isStopValue($eventContext, $returnValue)
203
    {
204
        return !$eventContext->isReturnValueIgnored() && $returnValue;
205
    }
206
207
    /**
208
     * Removes an item by key from the array if it is considered to be empty.
209
     *
210
     * @param array $array
211
     * @param mixed $key
212
     */
213
    protected function removeEmptyItem(&$array, $key)
214
    {
215
        if (empty($array[$key])) {
216
            unset($array[$key]);
217
        }
218
    }
219
}
220