Completed
Push — master ( 79f926...e307c0 )
by Michael
06:47
created

Mediator::walkEventList()   D

Complexity

Conditions 9
Paths 7

Size

Total Lines 28
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 28
rs 4.909
cc 9
eloc 13
nc 7
nop 2
1
<?php
2
declare(strict_types = 1);
3
/**
4
 * Contains Mediator class.
5
 *
6
 * PHP version 7.0
7
 *
8
 * LICENSE:
9
 * This file is part of Event Mediator - A general event mediator (dispatcher)
10
 * which has minimal dependencies so it is easy to drop in and use.
11
 * Copyright (C) 2015-2016 Michael Cummings
12
 *
13
 * This program is free software; you can redistribute it and/or modify it
14
 * under the terms of the GNU General Public License as published by the Free
15
 * Software Foundation; version 2 of the License.
16
 *
17
 * This program is distributed in the hope that it will be useful, but WITHOUT
18
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
19
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
20
 * details.
21
 *
22
 * You should have received a copy of the GNU General Public License along with
23
 * this program; if not, you may write to
24
 *
25
 * Free Software Foundation, Inc.
26
 * 59 Temple Place, Suite 330
27
 * Boston, MA 02111-1307 USA
28
 *
29
 * or find a electronic copy at
30
 * <http://spdx.org/licenses/GPL-2.0.html>.
31
 *
32
 * You should also be able to find a copy of this license in the included
33
 * LICENSE file.
34
 *
35
 * @author    Michael Cummings <[email protected]>
36
 * @copyright 2015-2016 Michael Cummings
37
 * @license   GPL-2.0
38
 */
39
namespace EventMediator;
40
41
/**
42
 * Class Mediator
43
 */
44
class Mediator implements MediatorInterface
45
{
46
    /**
47
     * @param string     $eventName
48
     * @param callable   $listener
49
     * @param int|string $priority
50
     *
51
     * @return MediatorInterface Fluent interface
52
     * @throws \DomainException
53
     * @throws \InvalidArgumentException
54
     */
55
    public function addListener(string $eventName, callable $listener, $priority = 0): MediatorInterface
56
    {
57
        $this->checkEventName($eventName);
58
        $priority = $this->getActualPriority($eventName, $priority);
59
        if (!(array_key_exists($eventName, $this->listeners)
60
            && array_key_exists($priority, $this->listeners[$eventName])
61
            && in_array($listener, $this->listeners[$eventName][$priority], true))
62
        ) {
63
            $this->listeners[$eventName][$priority][] = $listener;
64
        }
65
        return $this;
66
    }
67
    /**
68
     * @param array $events
69
     *
70
     * @return MediatorInterface Fluent interface.
71
     * @throws \DomainException
72
     * @throws \InvalidArgumentException
73
     * @throws \LengthException
74
     */
75
    public function addListenersByEventList(array $events): MediatorInterface
76
    {
77
        $this->walkEventList($events, [$this, 'addListener']);
78
        return $this;
79
    }
80
    /**
81
     * @param SubscriberInterface $sub
82
     *
83
     * @return MediatorInterface Fluent interface
84
     * @throws \DomainException
85
     * @throws \InvalidArgumentException
86
     * @throws \LengthException
87
     */
88
    public function addSubscriber(SubscriberInterface $sub): MediatorInterface
89
    {
90
        return $this->addListenersByEventList($sub->getSubscribedEvents());
91
    }
92
    /**
93
     * @param string $eventName
94
     *
95
     * @return array
96
     * @throws \InvalidArgumentException
97
     */
98
    public function getListeners(string $eventName = ''): array
99
    {
100
        $this->sortListeners($eventName);
101
        if ('' !== $eventName) {
102
            return array_key_exists($eventName, $this->listeners) ? $this->listeners[$eventName] : [];
103
        }
104
        return $this->listeners;
105
    }
106
    /**
107
     * @param string $eventName
108
     *
109
     * @return bool
110
     * @throws \InvalidArgumentException
111
     */
112
    public function hasListeners(string $eventName = ''): bool
113
    {
114
        return (bool)$this->getListeners($eventName);
115
    }
116
    /**
117
     * @param string     $eventName
118
     * @param callable   $listener
119
     * @param int|string $priority
120
     *
121
     * @return MediatorInterface Fluent interface
122
     * @throws \DomainException
123
     * @throws \InvalidArgumentException
124
     */
125
    public function removeListener(string $eventName, callable $listener, $priority = 0): MediatorInterface
126
    {
127
        $this->checkEventName($eventName);
128
        if (!array_key_exists($eventName, $this->listeners)) {
129
            return $this;
130
        }
131
        /**
132
         * @var array      $priorities
133
         * @var int        $atPriority
134
         * @var callable[] $listeners
135
         */
136
        if ('last' !== $priority) {
1 ignored issue
show
Unused Code Bug introduced by
The strict comparison !== seems to always evaluate to true as the types of 'last' (string) and $priority (integer) can never be identical. Maybe you want to use a loose comparison != instead?
Loading history...
137
            $priorities = $this->listeners[$eventName];
138
        } else {
139
            $priorities = array_reverse($this->listeners[$eventName], true);
140
            $priority = 'first';
141
        }
142
        foreach ($priorities as $atPriority => $listeners) {
143
            $key = array_search($listener, $listeners, true);
144
            if (false !== $key) {
145
                unset($this->listeners[$eventName][$atPriority][$key]);
146
                if (0 === count($this->listeners[$eventName][$atPriority])) {
147
                    unset($this->listeners[$eventName][$atPriority]);
148
                }
149
                if (0 === count($this->listeners[$eventName])) {
150
                    unset($this->listeners[$eventName]);
151
                }
152
                if ('first' === $priority) {
153
                    break;
154
                }
155
            }
156
        }
157
        return $this;
158
    }
159
    /**
160
     * @param array $events Events to be removed.
161
     *
162
     * @return MediatorInterface Fluent interface.
163
     * @throws \DomainException
164
     * @throws \InvalidArgumentException
165
     * @throws \LengthException
166
     */
167
    public function removeListenersByEventList(array $events): MediatorInterface
168
    {
169
        $this->walkEventList($events, [$this, 'removeListener']);
170
        return $this;
171
    }
172
    /**
173
     * @param SubscriberInterface $sub
174
     *
175
     * @return MediatorInterface Fluent interface
176
     * @throws \DomainException
177
     * @throws \InvalidArgumentException
178
     * @throws \LengthException
179
     */
180
    public function removeSubscriber(SubscriberInterface $sub): MediatorInterface
181
    {
182
        return $this->removeListenersByEventList($sub->getSubscribedEvents());
183
    }
184
    /**
185
     * @param string              $eventName
186
     * @param EventInterface|null $event
187
     *
188
     * @return EventInterface
189
     * @throws \DomainException
190
     * @throws \InvalidArgumentException
191
     */
192
    public function trigger(string $eventName, EventInterface $event = null): EventInterface
193
    {
194
        $this->checkEventName($eventName);
195
        if (null === $event) {
196
            $event = new Event();
197
        }
198
        $priorities = $this->getListeners($eventName);
199
        if (0 !== count($priorities)) {
200
            /**
201
             * @var array    $listeners
202
             * @var callable $listener
203
             */
204
            foreach ($priorities as $listeners) {
205
                foreach ($listeners as $listener) {
206
                    $listener($event, $eventName, $this);
207
                    /** @noinspection DisconnectedForeachInstructionInspection */
208
                    if ($event->hasBeenHandled()) {
209
                        break 2;
210
                    }
211
                }
212
            }
213
        }
214
        return $event;
215
    }
216
    /**
217
     * @param $eventName
218
     *
219
     * @throws \DomainException
220
     * @throws \InvalidArgumentException
221
     */
222
    protected function checkEventName(string $eventName)
223
    {
224
        if ('' === $eventName) {
225
            $mess = 'Event name can NOT be empty';
226
            throw new \DomainException($mess);
227
        }
228
        if (!ctype_print($eventName)) {
229
            $mess = 'Using any non-printable characters in the event name is NOT allowed';
230
            throw new \DomainException($mess);
231
        }
232
    }
233
    /**
234
     * @param string     $eventName
235
     * @param string|int $priority
236
     *
237
     * @return int
238
     */
239
    protected function getActualPriority(string $eventName, $priority): int
240
    {
241
        if ($priority === 'first') {
242
            $priority = !empty($this->listeners[$eventName]) ? max(array_keys($this->listeners[$eventName])) + 1 : 1;
243
            return (int)$priority;
244
        } elseif ($priority === 'last') {
245
            $priority = !empty($this->listeners[$eventName]) ? min(array_keys($this->listeners[$eventName])) - 1 : -1;
246
            return (int)$priority;
247
        }
248
        return (int)$priority;
249
    }
250
    /**
251
     * @param array    $events
252
     * @param callable $callback
253
     *
254
     * @throws \LengthException
255
     */
256
    protected function walkEventList(array $events, callable $callback)
257
    {
258
        if (0 === count($events)) {
259
            return;
260
        }
261
        /**
262
         * @var string     $eventName
263
         * @var array      $priorities
264
         * @var int|string $priority
265
         * @var array      $listeners
266
         * @var array      $listener
267
         */
268
        foreach ($events as $eventName => $priorities) {
269
            if (!is_array($priorities) || 0 === count($priorities)) {
270
                $mess = 'Must have as least one priority per listed event';
271
                throw new \LengthException($mess);
272
            }
273
            foreach ($priorities as $priority => $listeners) {
274
                if (!is_array($listeners) || 0 === count($listeners)) {
275
                    $mess = 'Must have at least one listener per listed priority';
276
                    throw new \LengthException($mess);
277
                }
278
                foreach ($listeners as $listener) {
279
                    $callback($eventName, $listener, $priority);
280
                }
281
            }
282
        }
283
    }
284
    /**
285
     * @param string $eventName
286
     *
287
     * @return MediatorInterface Fluent interface
288
     * @throws \InvalidArgumentException
289
     */
290
    private function sortListeners(string $eventName = ''): MediatorInterface
291
    {
292
        if (0 === count($this->listeners)) {
293
            return $this;
294
        }
295
        if ('' !== $eventName) {
296
            if (!array_key_exists($eventName, $this->listeners)) {
297
                return $this;
298
            }
299
            $eventNames = [$eventName];
300
        } else {
301
            ksort($this->listeners);
302
            $eventNames = array_keys($this->listeners);
303
        }
304
        foreach ($eventNames as $anEvent) {
305
            krsort($this->listeners[$anEvent], SORT_NUMERIC);
306
        }
307
        return $this;
308
    }
309
    /**
310
     * @var array $listeners
311
     */
312
    private $listeners = [];
313
}
314