Mediator   B
last analyzed

Complexity

Total Complexity 51

Size/Duplication

Total Lines 284
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 3

Test Coverage

Coverage 99.01%

Importance

Changes 0
Metric Value
wmc 51
lcom 2
cbo 3
dl 0
loc 284
ccs 100
cts 101
cp 0.9901
rs 7.92
c 0
b 0
f 0

14 Methods

Rating   Name   Duplication   Size   Complexity  
A addListener() 0 12 4
A addListenersByEventList() 0 5 1
A addSubscriber() 0 4 1
A getListeners() 0 8 3
A hasListeners() 0 4 1
B removeListener() 0 32 8
A removeListenersByEventList() 0 5 1
A removeSubscriber() 0 4 1
B trigger() 0 24 6
A checkEventName() 0 11 3
A getActualPriority() 0 12 5
B walkEventList() 0 28 9
A bubbleUpUnsetListener() 0 10 3
A sortListeners() 0 19 5

How to fix   Complexity   

Complex Class

Complex classes like Mediator often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Mediator, and based on these observations, apply Extract Interface, too.

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 23
    public function addListener(string $eventName, callable $listener, $priority = 0): MediatorInterface
56
    {
57 23
        $this->checkEventName($eventName);
58 21
        $priority = $this->getActualPriority($eventName, $priority);
59 21
        if (!(\array_key_exists($eventName, $this->listeners)
60 21
            && \array_key_exists($priority, $this->listeners[$eventName])
61 21
            && \in_array($listener, $this->listeners[$eventName][$priority], \true))
62
        ) {
63 21
            $this->listeners[$eventName][$priority][] = $listener;
64
        }
65 21
        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 7
    public function addListenersByEventList(array $events): MediatorInterface
76
    {
77 7
        $this->walkEventList($events, [$this, 'addListener']);
78 5
        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 5
    public function addSubscriber(SubscriberInterface $sub): MediatorInterface
89
    {
90 5
        return $this->addListenersByEventList($sub->getSubscribedEvents());
91
    }
92
    /**
93
     * @param string $eventName
94
     *
95
     * @return array
96
     * @throws \InvalidArgumentException
97
     */
98 24
    public function getListeners(string $eventName = ''): array
99
    {
100 24
        $this->sortListeners($eventName);
101 24
        if ('' !== $eventName) {
102 14
            return \array_key_exists($eventName, $this->listeners) ? $this->listeners[$eventName] : [];
103
        }
104 19
        return $this->listeners;
105
    }
106
    /**
107
     * @param string $eventName
108
     *
109
     * @return bool
110
     * @throws \InvalidArgumentException
111
     */
112 1
    public function hasListeners(string $eventName = ''): bool
113
    {
114 1
        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 6
    public function removeListener(string $eventName, callable $listener, $priority = 0): MediatorInterface
126
    {
127 6
        $this->checkEventName($eventName);
128 5
        if (!\array_key_exists($eventName, $this->listeners)) {
129 1
            return $this;
130
        }
131
        /**
132
         * @var array      $priorities
133
         * @var int        $atPriority
134
         * @var callable[] $listeners
135
         */
136 4
        if ('last' !== $priority) {
137 4
            $priorities = $this->listeners[$eventName];
138
        } else {
139 1
            $priorities = \array_reverse($this->listeners[$eventName], \true);
140 1
            $priority = 'first';
141
        }
142 4
        $isIntPriority = \is_int($priority);
143 4
        foreach ($priorities as $atPriority => $listeners) {
144 4
            if ($isIntPriority && $priority !== $atPriority) {
145 1
                continue;
146
            }
147 4
            $key = \array_search($listener, $listeners, \true);
148 4
            if (\false !== $key) {
149 4
                $this->bubbleUpUnsetListener($eventName, $atPriority, $key);
150 4
                if ('first' === $priority) {
151 4
                    break;
152
                }
153
            }
154
        }
155 4
        return $this;
156
    }
157
    /**
158
     * @param array $events Events to be removed.
159
     *
160
     * @return MediatorInterface Fluent interface.
161
     * @throws \DomainException
162
     * @throws \InvalidArgumentException
163
     * @throws \LengthException
164
     */
165 2
    public function removeListenersByEventList(array $events): MediatorInterface
166
    {
167 2
        $this->walkEventList($events, [$this, 'removeListener']);
168 2
        return $this;
169
    }
170
    /**
171
     * @param SubscriberInterface $sub
172
     *
173
     * @return MediatorInterface Fluent interface
174
     * @throws \DomainException
175
     * @throws \InvalidArgumentException
176
     * @throws \LengthException
177
     */
178 2
    public function removeSubscriber(SubscriberInterface $sub): MediatorInterface
179
    {
180 2
        return $this->removeListenersByEventList($sub->getSubscribedEvents());
181
    }
182
    /**
183
     * @param string              $eventName
184
     * @param EventInterface|null $event
185
     *
186
     * @return EventInterface
187
     * @throws \DomainException
188
     * @throws \InvalidArgumentException
189
     */
190 11
    public function trigger(string $eventName, EventInterface $event = \null): EventInterface
191
    {
192 11
        $this->checkEventName($eventName);
193 10
        if (\null === $event) {
194 1
            $event = new Event();
195
        }
196 10
        $priorities = $this->getListeners($eventName);
197 10
        if (0 !== \count($priorities)) {
198
            /**
199
             * @var array    $listeners
200
             * @var callable $listener
201
             */
202 8
            foreach ($priorities as $listeners) {
203 8
                foreach ($listeners as $listener) {
204 8
                    $listener($event, $eventName, $this);
205
                    /** @noinspection DisconnectedForeachInstructionInspection */
206 8
                    if ($event->hasBeenHandled()) {
207 8
                        break 2;
208
                    }
209
                }
210
            }
211
        }
212 10
        return $event;
213
    }
214
    /**
215
     * @param $eventName
216
     *
217
     * @throws \DomainException
218
     * @throws \InvalidArgumentException
219
     */
220 46
    protected function checkEventName(string $eventName)
221
    {
222 46
        if ('' === $eventName) {
223 5
            $mess = 'Event name can NOT be empty';
224 5
            throw new \DomainException($mess);
225
        }
226 41
        if (!\ctype_print($eventName)) {
227 1
            $mess = 'Using any non-printable characters in the event name is NOT allowed';
228 1
            throw new \DomainException($mess);
229
        }
230
    }
231
    /**
232
     * @param string     $eventName
233
     * @param string|int $priority
234
     *
235
     * @return int
236
     */
237 19
    protected function getActualPriority(string $eventName, $priority): int
238
    {
239 19
        if ($priority === 'first') {
240 7
            $priority = !empty($this->listeners[$eventName]) ? \max(\array_keys($this->listeners[$eventName])) + 1 : 1;
241 7
            return $priority;
242
        }
243 17
        if ($priority === 'last') {
244 7
            $priority = !empty($this->listeners[$eventName]) ? \min(\array_keys($this->listeners[$eventName])) - 1 : -1;
245 7
            return $priority;
246
        }
247 16
        return $priority;
248
    }
249
    /**
250
     * @param array    $events
251
     * @param callable $callback
252
     *
253
     * @throws \LengthException
254
     */
255 13
    protected function walkEventList(array $events, callable $callback)
256
    {
257 13
        if (0 === \count($events)) {
258
            return;
259
        }
260
        /**
261
         * @var string     $eventName
262
         * @var array      $priorities
263
         * @var int|string $priority
264
         * @var array      $listeners
265
         * @var array      $listener
266
         */
267 13
        foreach ($events as $eventName => $priorities) {
268 13
            if (!\is_array($priorities) || 0 === \count($priorities)) {
269 1
                $mess = 'Must have as least one priority per listed event';
270 1
                throw new \LengthException($mess);
271
            }
272 12
            foreach ($priorities as $priority => $listeners) {
273 12
                if (!\is_array($listeners) || 0 === \count($listeners)) {
274 1
                    $mess = 'Must have at least one listener per listed priority';
275 1
                    throw new \LengthException($mess);
276
                }
277 11
                foreach ($listeners as $listener) {
278 11
                    $callback($eventName, $listener, $priority);
279
                }
280
            }
281
        }
282
    }
283
    /**
284
     * @param string $eventName
285
     * @param int    $priority
286
     * @param int    $key
287
     */
288 4
    private function bubbleUpUnsetListener(string $eventName, int $priority, int $key)
289
    {
290 4
        unset($this->listeners[$eventName][$priority][$key]);
291 4
        if (0 === \count($this->listeners[$eventName][$priority])) {
292 4
            unset($this->listeners[$eventName][$priority]);
293
        }
294 4
        if (0 === \count($this->listeners[$eventName])) {
295 4
            unset($this->listeners[$eventName]);
296
        }
297
    }
298
    /**
299
     * @param string $eventName
300
     *
301
     * @return MediatorInterface Fluent interface
302
     * @throws \InvalidArgumentException
303
     */
304 24
    private function sortListeners(string $eventName = ''): MediatorInterface
305
    {
306 24
        if (0 === \count($this->listeners)) {
307 12
            return $this;
308
        }
309 18
        if ('' !== $eventName) {
310 12
            if (!\array_key_exists($eventName, $this->listeners)) {
311 2
                return $this;
312
            }
313 11
            $eventNames = [$eventName];
314
        } else {
315 12
            \ksort($this->listeners);
316 12
            $eventNames = \array_keys($this->listeners);
317
        }
318 17
        foreach ($eventNames as $anEvent) {
319 17
            \krsort($this->listeners[$anEvent], \SORT_NUMERIC);
320
        }
321 17
        return $this;
322
    }
323
    /**
324
     * @var array $listeners
325
     */
326
    private $listeners = [];
327
}
328