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

removeServiceSubscriberByEventList()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 20
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

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

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
356
        }
357
        foreach ($eventNames as $anEvent) {
358
            krsort($this->serviceListeners[$anEvent], SORT_NUMERIC);
359
        }
360
        return $this;
361
    }
362
    /**
363
     * List of already loaded services.
364
     *
365
     * @var string[] $loadedServices
366
     */
367
    private $loadedServices = [];
368
    /**
369
     * @var array $serviceListeners Holds the list of service listeners that will be lazy loaded when events are
370
     * triggered.
371
     */
372
    private $serviceListeners = [];
373
}
374