AbstractContainerMediator   C
last analyzed

Complexity

Total Complexity 53

Size/Duplication

Total Lines 345
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 2

Test Coverage

Coverage 98.26%

Importance

Changes 0
Metric Value
wmc 53
lcom 2
cbo 2
dl 0
loc 345
ccs 113
cts 115
cp 0.9826
rs 6.96
c 0
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
getServiceByName() 0 1 ?
setServiceContainer() 0 1 ?
A addServiceListener() 0 14 4
A addServiceListenersByEventList() 0 5 1
A addServiceSubscriber() 0 4 1
A getListeners() 0 11 4
A getServiceListeners() 0 8 3
B removeServiceListener() 0 36 9
A removeServiceListenersByEventList() 0 5 1
A removeServiceSubscriber() 0 4 1
B getActualPriority() 0 19 8
A bubbleUpUnsetServiceListener() 0 14 3
B checkAllowedServiceListener() 0 27 8
A lazyLoadServices() 0 21 5
A sortServiceListeners() 0 19 5

How to fix   Complexity   

Complex Class

Complex classes like AbstractContainerMediator 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 AbstractContainerMediator, and based on these observations, apply Extract Interface, too.

1
<?php
2
declare(strict_types=1);
3
/**
4
 * Contains AbstractContainerMediator 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
40
namespace EventMediator;
41
42
/**
43
 * Class AbstractContainerMediator
44
 */
45
abstract class AbstractContainerMediator extends Mediator implements ContainerMediatorInterface
46
{
47
    /**
48
     * Add a service as an event listener.
49
     *
50
     * @param string     $eventName Name of the event the listener is being added for.
51
     * @param array      $listener  Listener to be added. ['containerID', 'method']
52
     * @param int|string $priority  Priority level for the added listener.
53
     *
54
     * @return ContainerMediatorInterface Fluent interface.
55
     * @throws \DomainException
56
     * @throws \InvalidArgumentException
57
     */
58 19
    public function addServiceListener(string $eventName, array $listener, $priority = 0): ContainerMediatorInterface
59
    {
60 19
        $this->checkEventName($eventName);
61 18
        $this->checkAllowedServiceListener($listener);
62 13
        $priority = $this->getActualPriority($eventName, $priority);
63 12
        if (\array_key_exists($eventName, $this->serviceListeners)
64 12
            && \array_key_exists($priority, $this->serviceListeners[$eventName])
65 12
            && \in_array($listener, $this->serviceListeners[$eventName][$priority], \true)
66
        ) {
67 1
            return $this;
68
        }
69 12
        $this->serviceListeners[$eventName][$priority][] = $listener;
70 12
        return $this;
71
    }
72
    /**
73
     * @param array $events
74
     *
75
     * @return ContainerMediatorInterface
76
     * @throws \DomainException
77
     * @throws \InvalidArgumentException
78
     * @throws \LengthException
79
     */
80 6
    public function addServiceListenersByEventList(array $events): ContainerMediatorInterface
81
    {
82 6
        $this->walkEventList($events, [$this, 'addServiceListener']);
83 6
        return $this;
84
    }
85
    /**
86
     * Add a service as a subscriber to event(s).
87
     *
88
     * @param ServiceSubscriberInterface $sub Service subscriber to be added.
89
     *
90
     * @return ContainerMediatorInterface Fluent interface.
91
     * @throws \DomainException
92
     * @throws \InvalidArgumentException
93
     * @throws \LengthException
94
     */
95 6
    public function addServiceSubscriber(ServiceSubscriberInterface $sub): ContainerMediatorInterface
96
    {
97 6
        return $this->addServiceListenersByEventList($sub->getServiceSubscribedEvents());
98
    }
99
    /**
100
     * @param string $eventName
101
     *
102
     * @return array
103
     * @throws \DomainException
104
     * @throws \InvalidArgumentException
105
     */
106 5
    public function getListeners(string $eventName = ''): array
107
    {
108 5
        if (0 !== \count($this->serviceListeners)) {
109 3
            if ('' === $eventName) {
110
                $this->lazyLoadServices(\array_keys($this->serviceListeners));
111 3
            } elseif (\array_key_exists($eventName, $this->serviceListeners)) {
112 3
                $this->lazyLoadServices([$eventName]);
113
            }
114
        }
115 5
        return parent::getListeners($eventName);
116
    }
117
    /** @noinspection GenericObjectTypeUsageInspection */
118
    /**
119
     * This method is used any time the mediator need to get the actual instance
120
     * of the class for an event.
121
     *
122
     * Normal will only be called during actual trigger of an event since lazy
123
     * loading is used.
124
     *
125
     * @param string $serviceName
126
     *
127
     * @return object
128
     */
129
    abstract public function getServiceByName(string $serviceName);
130
    /**
131
     * Get a list of service listeners for an event.
132
     *
133
     * Note that if event name is empty all listeners will be returned. Any event subscribers are also included in the
134
     * list.
135
     *
136
     * @param string $eventName Name of the event the list of service listeners is needed for.
137
     *
138
     * @return array List of event service listeners or empty array if event is unknown or has no listeners or
139
     *               subscribers.
140
     * @throws \InvalidArgumentException
141
     */
142 9
    public function getServiceListeners(string $eventName = ''): array
143
    {
144 9
        $this->sortServiceListeners($eventName);
145 9
        if ('' !== $eventName) {
146 3
            return \array_key_exists($eventName, $this->serviceListeners) ? $this->serviceListeners[$eventName] : [];
147
        }
148 7
        return $this->serviceListeners;
149
    }
150
    /**
151
     * Remove a service as an event listener.
152
     *
153
     * @param string     $eventName Event name that listener is being removed from.
154
     * @param array      $listener  Service listener to be removed.
155
     * @param int|string $priority  Priority level for the to be removed listener.
156
     *
157
     * @return ContainerMediatorInterface Fluent interface.
158
     * @throws \DomainException
159
     * @throws \InvalidArgumentException
160
     */
161 7
    public function removeServiceListener(string $eventName, array $listener, $priority = 0): ContainerMediatorInterface
162
    {
163 7
        $this->checkEventName($eventName);
164 6
        if (!\array_key_exists($eventName, $this->serviceListeners)) {
165 1
            return $this;
166
        }
167 5
        $this->checkAllowedServiceListener($listener);
168 4
        if (\in_array($eventName, $this->loadedServices, \true)) {
169 1
            $this->removeListener($eventName, [$this->getServiceByName($listener[0]), $listener[1]], $priority);
170
        }
171
        /**
172
         * @var array      $priorities
173
         * @var int|string $atPriority
174
         * @var array      $listeners
175
         */
176 4
        if ('last' !== $priority) {
177 4
            $priorities = $this->serviceListeners[$eventName];
178
        } else {
179 1
            $priorities = \array_reverse($this->serviceListeners[$eventName], \true);
180 1
            $priority = 'first';
181
        }
182 4
        $isIntPriority = \is_int($priority);
183 4
        foreach ($priorities as $atPriority => $listeners) {
184 4
            if ($isIntPriority && $priority !== $atPriority) {
185
                continue;
186
            }
187 4
            $key = \array_search($listener, $listeners, \true);
188 4
            if (\false !== $key) {
189 4
                $this->bubbleUpUnsetServiceListener($eventName, $atPriority, $key);
190 4
                if ('first' === $priority) {
191 4
                    break;
192
                }
193
            }
194
        }
195 4
        return $this;
196
    }
197
    /**
198
     * @param array $events
199
     *
200
     * @return ContainerMediatorInterface
201
     * @throws \DomainException
202
     * @throws \InvalidArgumentException
203
     * @throws \LengthException
204
     */
205 3
    public function removeServiceListenersByEventList(array $events): ContainerMediatorInterface
206
    {
207 3
        $this->walkEventList($events, [$this, 'removeServiceListener']);
208 3
        return $this;
209
    }
210
    /**
211
     * Remove a service subscriber from event(s).
212
     *
213
     * @param ServiceSubscriberInterface $sub Subscriber to be removed.
214
     *
215
     * @return ContainerMediatorInterface Fluent interface.
216
     * @throws \DomainException
217
     * @throws \InvalidArgumentException
218
     * @throws \LengthException
219
     */
220 3
    public function removeServiceSubscriber(ServiceSubscriberInterface $sub): ContainerMediatorInterface
221
    {
222 3
        return $this->removeServiceListenersByEventList($sub->getServiceSubscribedEvents());
223
    }
224
    /**
225
     * This is used to bring in the service container that will be used.
226
     *
227
     * Though not required it would be considered best practice for this method
228
     * to create a new instance of the container when given null. Another good
229
     * practice is to call this method from the class constructor to allow
230
     * easier testing. For examples of both have a look at
231
     * PimpleContainerMediator.
232
     *
233
     * @see PimpleContainerMediator Container mediator implemented using Pimple.
234
     *
235
     * @param mixed $value The service container to be used.
236
     *
237
     * @return ContainerMediatorInterface Fluent interface.
238
     */
239
    abstract public function setServiceContainer($value = \null): ContainerMediatorInterface;
240
    /**
241
     * @param string     $eventName
242
     * @param string|int $priority
243
     *
244
     * @return int
245
     * @throws \InvalidArgumentException
246
     */
247 15
    protected function getActualPriority(string $eventName, $priority): int
248
    {
249 15
        if (\is_int($priority)) {
250 14
            return $priority;
251
        }
252 4
        if (!\in_array($priority, ['first', 'last'], \true)) {
253 1
            $mess = 'Unknown priority was given only "first", "last", or integer may be used';
254 1
            throw new \InvalidArgumentException($mess);
255
        }
256 3
        $listenerM = parent::getActualPriority($eventName, $priority);
257 3
        if ($priority === 'first') {
258 2
            $serviceM = \array_key_exists($eventName, $this->serviceListeners)
259 2
                ? \max(\array_keys($this->serviceListeners[$eventName])) + 1 : 1;
260 2
            return ($listenerM > $serviceM) ? $listenerM : $serviceM;
261
        }
262 1
        $serviceM = \array_key_exists($eventName, $this->serviceListeners)
263 1
            ? \min(\array_keys($this->serviceListeners[$eventName])) - 1 : -1;
264 1
        return ($listenerM < $serviceM) ? $listenerM : $serviceM;
265
    }
266
    /**
267
     * @param string $eventName
268
     * @param int    $priority
269
     * @param int    $key
270
     */
271 4
    private function bubbleUpUnsetServiceListener(string $eventName, int $priority, int $key)
272
    {
273 4
        unset($this->serviceListeners[$eventName][$priority][$key]);
274
        // Remove empty priorities.
275 4
        if (0 === \count($this->serviceListeners[$eventName][$priority])) {
276 4
            unset($this->serviceListeners[$eventName][$priority]);
277
        }
278
        // Remove empty events.
279 4
        if (0 === \count($this->serviceListeners[$eventName])) {
280 4
            unset($this->serviceListeners[$eventName]);
281 4
            $key = (int)\array_search($eventName, $this->loadedServices, \true);
282 4
            unset($this->loadedServices[$key]);
283
        }
284
    }
285
    /**
286
     * @param $listener
287
     *
288
     * @throws \InvalidArgumentException
289
     */
290 18
    private function checkAllowedServiceListener($listener)
291
    {
292 18
        if (\is_array($listener) && 2 === \count($listener)) {
293 17
            list($containerID, $method) = $listener;
294 17
            if (!\is_string($method)) {
295 2
                $mess = \sprintf('Service listener method name MUST be a string, but was given %s', \gettype($method));
296 2
                throw new \InvalidArgumentException($mess);
297
            }
298 16
            if (!\is_string($containerID)) {
299 1
                $mess = \sprintf('Service listener container ID MUST be a string, but was given %s',
300 1
                    \gettype($containerID));
301 1
                throw new \InvalidArgumentException($mess);
302
            }
303 15
            if (!\ctype_print($method) || \false === \preg_match('%\w{1,}%', $method)) {
304 1
                $mess = 'Service listener method name format is invalid, was given ' . $method;
305 1
                throw new \InvalidArgumentException($mess);
306
            }
307
            // Also catches empty string.
308 14
            if (!\ctype_print($containerID)) {
309 1
                $mess = 'Using any non-printable characters in the container ID is NOT allowed';
310 1
                throw new \InvalidArgumentException($mess);
311
            }
312 13
            return;
313
        }
314 1
        $mess = 'Service listener form MUST be ["containerID", "methodName"]';
315 1
        throw new \InvalidArgumentException($mess);
316
    }
317
    /**
318
     * @param string[] $eventNames
319
     *
320
     * @return ContainerMediatorInterface Fluent interface
321
     * @throws \DomainException
322
     * @throws \InvalidArgumentException
323
     */
324 3
    private function lazyLoadServices(array $eventNames): ContainerMediatorInterface
325
    {
326 3
        foreach ($eventNames as $event) {
327 3
            if (!\in_array($event, $this->loadedServices, \true)) {
328 3
                $this->loadedServices[] = $event;
329
            }
330
            /**
331
             * @var array $priorities
332
             * @var int   $priority
333
             * @var array $listeners
334
             * @var array $listener
335
             */
336 3
            $priorities = $this->serviceListeners[$event];
337 3
            foreach ($priorities as $priority => $listeners) {
338 3
                foreach ($listeners as $listener) {
339 3
                    $this->addListener($event, [$this->getServiceByName($listener[0]), $listener[1]], $priority);
340
                }
341
            }
342
        }
343 3
        return $this;
344
    }
345
    /**
346
     * @param string $eventName
347
     *
348
     * @return ContainerMediatorInterface Fluent Interface
349
     * @throws \InvalidArgumentException
350
     */
351 9
    private function sortServiceListeners(string $eventName): ContainerMediatorInterface
352
    {
353 9
        if (0 === \count($this->serviceListeners)) {
354 4
            return $this;
355
        }
356 8
        if ('' !== $eventName) {
357 3
            if (!\array_key_exists($eventName, $this->serviceListeners)) {
358 2
                return $this;
359
            }
360 2
            $eventNames = [$eventName];
361
        } else {
362 6
            \ksort($this->serviceListeners);
363 6
            $eventNames = \array_keys(parent::getListeners());
364
        }
365 7
        foreach ($eventNames as $anEvent) {
366 2
            \krsort($this->serviceListeners[$anEvent], \SORT_NUMERIC);
367
        }
368 7
        return $this;
369
    }
370
    /**
371
     * List of already loaded services.
372
     *
373
     * @var array $loadedServices
374
     */
375
    private $loadedServices = [];
376
    /**
377
     * Holds list of service listeners.
378
     *
379
     * {@internal Actual Generics-style notation would be:
380
     *      array<string,array<int,array<int,array<string>>>>
381
     *      or put another way:
382
     *      $serviceListeners[string eventName][int priority][int]= ["containerID", "methodName"];
383
     * }
384
     *
385
     * @var array $serviceListeners Holds the list of service listeners that will be lazy loaded when events are
386
     * triggered.
387
     */
388
    private $serviceListeners = [];
389
}
390