Completed
Push — dev ( bbaf77...ba12db )
by Aleksander
02:00
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
                if (empty($this->listeners[$eventName][$priority])) {
103
                    unset($this->listeners[$eventName][$priority]);
104
                }
105
            }
106
        }
107
108
        if (empty($this->listeners[$eventName])) {
109
            unset($this->listeners[$eventName]);
110
        }
111
    }
112
113
    public function hasListeners($eventName = null)
114
    {
115
        return $eventName !== null ? !empty($this->listeners[$eventName]) : !empty($this->listeners);
116
    }
117
118
    /**
119
     * Sorts internal listeners array by priority.
120
     *
121
     * @return void
122
     */
123
    protected function sortListenersByPriority()
124
    {
125
        if ($this->listenersSorted) {
126
            return;
127
        }
128
129
        foreach (array_keys($this->listeners) as $eventName) {
130
            ksort($this->listeners[$eventName]);
131
        }
132
133
        $this->listenersSorted = true;
134
    }
135
136
    /**
137
     * @param EventContext $eventContext
138
     * @param callable $listener
139
     * @param array $parameters
140
     *
141
     * @return void
142
     */
143
    protected function invokeListener($eventContext, $listener, $parameters = [])
144
    {
145
        if ($eventContext->isStopped()) {
146
            $eventContext->addStoppedListener($listener);
147
            return;
148
        }
149
150
        $eventContext->ignoreReturnValue(false);
151
        $returnValue = $this->invoker->call($listener, $parameters);
152
153
        if ($eventContext->isStopped()) {
154
            $eventContext->setStopValue(true);
155
            
156
        } else if ($this->isStopValue($eventContext, $returnValue)) {
157
            $eventContext->setStopped(true);
158
            $eventContext->setStopValue($returnValue);
159
        }
160
161
        $eventContext->addExecutedListener($listener);
162
    }
163
164
    /**
165
     * Injects predefined disptacher 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
        if (\array_intersect_key($parameters, \array_flip(['eventName', 'eventContext', 'eventDispatcher']))) {
177
            throw new OverriddenParameter('Following parameters cannot be passed in parameters array: eventName, eventContext, eventDispatcher.');
178
        }
179
180
        $parameters['eventName'] = $eventContext->getEventName();
181
        $parameters['eventContext'] = $eventContext;
182
        $parameters['eventDispatcher'] = $this;
183
    }
184
185
    /**
186
     * @param mixed $priority
187
     *
188
     * @throws InvalidPriority
189
     *
190
     * @return void
191
     */
192
    protected function validatePriority($priority)
193
    {
194
        if (!is_int($priority) || $priority < 0) {
195
            throw new InvalidPriority('Expected non-negative integer priority. Provided: ' . $priority);
196
        }
197
    }
198
199
    /**
200
     * @param EventContext $eventContext
201
     * @param mixed $returnValue
202
     *
203
     * @return bool
204
     */
205
    protected function isStopValue($eventContext, $returnValue)
206
    {
207
        return !$eventContext->isReturnValueIgnored() && $returnValue;
208
    }
209
}
210