EventManagerAbstractFactory::normalizeEventsSpec()   F
last analyzed

Complexity

Conditions 18
Paths 540

Size

Total Lines 46
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 30
CRAP Score 18

Importance

Changes 0
Metric Value
eloc 29
dl 0
loc 46
c 0
b 0
f 0
ccs 30
cts 30
cp 1
rs 1.3388
cc 18
nc 540
nop 1
crap 18

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * YAWIK
4
 *
5
 * @filesource
6
 * @license MIT
7
 * @copyright  2013 - 2016 Cross Solution <http://cross-solution.de>
8
 */
9
10
/** */
11
namespace Core\Factory\EventManager;
12
13
use Core\EventManager\EventProviderInterface;
14
use Interop\Container\ContainerInterface;
15
use Interop\Container\Exception\ContainerException;
16
use Laminas\EventManager\ListenerAggregateInterface;
17
use Laminas\ServiceManager\Factory\AbstractFactoryInterface;
18
use Laminas\ServiceManager\Exception\ServiceNotCreatedException;
19
use Laminas\ServiceManager\Exception\ServiceNotFoundException;
20
use Laminas\ServiceManager\ServiceLocatorInterface;
21
use Laminas\Stdlib\ArrayUtils;
22
23
/**
24
 * Creates event manager instances.
25
 *
26
 * Optionally configures these instances with options set in array with the key "event_manager" in the main config
27
 * array. Also creates listeners which are preconfigured in the options and automatically attaches them.
28
 *
29
 * The options array:
30
 * [
31
 *      'event_manager' => [
32
 *          'Meaningful/Service.Name/Events' => [
33
 *              'service' => string: Service name or class name of the event manager to create.
34
 *              'event'   => string: Service name or class name of the event class to be used.
35
 *              'configure' => bool: Wether or not to configure the service manager through THIS factory.
36
 *              'identifiers' => array: list of identifiers for the event manager.
37
 *              'listeners' => array: preconfigured listeners which will ONLY be created, when the
38
 *                                    event manager is created. (lazy loading)
39
 *      ]
40
 * ]
41
 *
42
 * The listeners array:
43
 *
44
 * [
45
 *      string:listener => string:event, // creates the listener with the key as name (or class) and attaches it
46
 *                                       // to the event manager on the provided event.
47
 *
48
 *      // If you need more options or need to attach to multiple events, you can use following syntax: //
49
 *      string:listener => [ string|array:event{, string:methodName}{, int:priority}{, bool:lazy }],
50
 *                      // First string item or any array item is used as event(s)
51
 *                      // Any string item (if event is already set) and any following string items set (and override previous) method names
52
 *                      // (the method name is the name of the method to be called upon the listener when the event happens.)
53
 *                      // Any int item set and override previously set priority.
54
 *                      // Any boolean item set and override previously set lazy option.
55
 *                      // (lazy option does provide even more lazy loading. The listener is only created, if
56
 *                      //  the event it listens to is actually triggered. (accomplished by \Core\Listener\DeferredListenerAggregate)
57
 *
58
 *      string:aggregate, // Creates an ListenerAggregate and call its attach method with the instance of the event manager
59
 *      string:aggregate => int:priority // Same as above, but passes the priority value along.
60
 * ]
61
 *
62
 * If you need to attach more than one method or two events with different priorities you can do so using the
63
 * verbose style in the listener array:
64
 *
65
 * [
66
 *      string:listener => [
67
 *          'events' => [
68
 *              string:eventName,
69
 *              string:eventName => int:priority,
70
 *              string:eventName' => string:method,
71
 *              string:eventName => [
72
 *                  'method' => string:method,
73
 *                  'method' => [ string:method, string:method, ... ],
74
 *                  'method' => [ string:method => int:priority, string:method' => int:priority ],
75
 *                  'priority' => int:priority,
76
 *              ],
77
 *          ],
78
 *          'method' => string:defaultMethod,
79
 *          'priority' => int:defaultPriority,
80
 *          'lazy' => bool
81
 *      ],
82
 * ]
83
 *
84
 * Example:
85
 * Attach one listener to two methods on one event:
86
 * [
87
 *      'Listener' => [ 'events' => [ 'eventName' => [ 'method' => ['method1', 'method2'] ] ] ],
88
 * ]
89
 *
90
 * Use default priority on method1 and different priority on method2:
91
 * [
92
 *      'Listener' => [ 'events' => [ 'eventName' => [ 'method' => ['method1', 'method2' => 12] ] ] ], 'priority' => 5 ],
93
 * ]
94
 *
95
 * Attach one listener to two events with different priority:
96
 * [
97
 *      'Listener' => [ 'events' => [ 'event1' => 1, 'event2' => 2 ] ],
98
 * ]
99
 *
100
 * Each specific event -> method -> priority triple will be attached searately to the event manager using the
101
 * same listener instance.
102
 *
103
 *
104
 * @author Mathias Gelhausen <[email protected]>
105
 * @since 0.25
106
 */
107
class EventManagerAbstractFactory implements AbstractFactoryInterface
108
{
109
    /**
110
     * Create an object
111
     *
112
     * @param  ContainerInterface $container
113
     * @param  string             $requestedName
114
     * @param  null|array         $options
115
     *
116
     * @return object
117
     * @throws ServiceNotFoundException if unable to resolve the service.
118
     * @throws ServiceNotCreatedException if an exception is raised when
119
     *     creating a service.
120
     * @throws ContainerException if any other error occurs
121
     */
122 14
    public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
123
    {
124 14
        $config = $this->getConfig($container, $requestedName);
125 14
        $events = $this->createEventManager($container, $config);
126
127 14
        $this->attachListeners($container, $events, $config['listeners']);
128 13
        return $events;
129
    }
130
131
    /**
132
     * Can the factory create an instance for the service?
133
     *
134
     * @param  ContainerInterface $container
135
     * @param  string             $requestedName
136
     *
137
     * @return bool
138
     */
139 7
    public function canCreate(ContainerInterface $container, $requestedName)
140
    {
141
        /* We check, if $requestedName ends with the string '/Events'.
142
         * Instead of parsing the string with regular expressions (eg. ~/Events$~),
143
         * it's more efficient to just check with strpos, if the reversed string starts
144
         * with the reverted '/Events' string.
145
         */
146 7
        return 0 === strpos(strrev($requestedName), 'stnevE/');
147
    }
148
149 1
    public function canCreateServiceWithName(ServiceLocatorInterface $serviceLocator, $name, $requestedName)
0 ignored issues
show
Unused Code introduced by
The parameter $name is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

149
    public function canCreateServiceWithName(ServiceLocatorInterface $serviceLocator, /** @scrutinizer ignore-unused */ $name, $requestedName)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
150
    {
151 1
        return $this->canCreate($serviceLocator, $requestedName);
152
    }
153
154
    /**
155
     * Creates an event manager and attaches preconfigured listeners.
156
     *
157
     * @param ServiceLocatorInterface $serviceLocator
158
     * @param string                  $name
159
     * @param string                  $requestedName
160
     *
161
     * @return \Laminas\EventManager\EventManagerInterface
162
     * @throws \UnexpectedValueException
163
     */
164 7
    public function createServiceWithName(ServiceLocatorInterface $serviceLocator, $name, $requestedName)
0 ignored issues
show
Unused Code introduced by
The parameter $name is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

164
    public function createServiceWithName(ServiceLocatorInterface $serviceLocator, /** @scrutinizer ignore-unused */ $name, $requestedName)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
165
    {
166 7
        return $this($serviceLocator, $requestedName);
167
    }
168
169
    /**
170
     * Gets configuration for an event manager.
171
     *
172
     * Merges the default config with configuration from the main config,
173
     * if a key $name exists in the array under the "event_manager" array in the main config.
174
     *
175
     * @param ServiceLocatorInterface $services
176
     * @param string $name
177
     *
178
     * @return array
179
     */
180 7
    protected function getConfig($services, $name)
181
    {
182
        $defaults = [
183 7
            'service' => 'EventManager',
184
            'configure' => true,
185 7
            'identifiers' => [ $name ],
186 7
            'event' => '\Laminas\EventManager\Event',
187
            'listeners' => [],
188
        ];
189
190 7
        $config = $services->get('Config');
191 7
        $config = isset($config['event_manager'][$name]) ? $config['event_manager'][$name] : [];
192
193
        /*
194
         * array_merge does not work, because the default values for 'identifiers' and 'listeners'
195
         * are arrays and array_merge breaks the structure.
196
         */
197 7
        $config = array_replace_recursive($defaults, $config);
198
199 7
        return $config;
200
    }
201
202
    /**
203
     * Creates an event manager instance.
204
     *
205
     * Fetches from the service manager or tries to instantiate direct, if no service
206
     * exists in the service manager.
207
     *
208
     * If the key 'configure' in the config array has the value TRUE (default),
209
     * the event manager instance will get configured. Which means, the event prototype
210
     * will be set (after it is fetched from the service manager or instatiated),
211
     * and the shared event manager will be injected.
212
     *
213
     * @param ServiceLocatorInterface $services
214
     * @param array $config
215
     *
216
     * @return \Laminas\EventManager\EventManagerInterface
217
     * @throws \UnexpectedValueException if neither a service exists, nor could a class be found.
218
     */
219 13
    protected function createEventManager($services, $config)
220
    {
221
        /* @var \Laminas\EventManager\EventManagerInterface|\Core\EventManager\EventProviderInterface $events */
222
223 13
        if ($services->has($config['service'])) {
224 11
            $events = $services->build($config['service']);
225
        } else {
226 2
            if (!class_exists($config['service'], true)) {
227 1
                throw new \UnexpectedValueException(sprintf(
228 1
                    'Class or service %s does not exists. Cannot create event manager instance.',
229 1
                    $config['service']
230
                ));
231
            }
232
233 1
            $events = new $config['service']();
234
        }
235
236 12
        if (false === $config['configure']) {
237 3
            return $events;
238
        }
239
240 9
        $events->setIdentifiers($config['identifiers']);
241
242
        /* @var \Laminas\EventManager\EventInterface $event */
243 9
        $event = $services->has($config['event']) ? $services->get($config['event']) : new $config['event']();
244 9
        $events->setEventPrototype($event);
245
246 9
        if ('EventManager' != $config['service'] && method_exists($events, 'setSharedManager') && $services->has('SharedEventManager')) {
247
            /* @var \Laminas\EventManager\SharedEventManagerInterface $sharedEvents */
248 1
            $sharedEvents = $services->get('SharedEventManager');
249 1
            $events->setSharedManager($sharedEvents);
250
        }
251
252 9
        return $events;
253
    }
254
255
    /**
256
     * Attaches listeners provided in the config to the event manager instance.
257
     *
258
     * @param ServiceLocatorInterface $services
259
     * @param \Laminas\EventManager\EventManagerInterface $eventManager
260
     * @param array $listeners
261
     *
262
     * @throws \UnexpectedValueException if a listener name cannot be fetched as service or be instantiated.
263
     */
264 12
    protected function attachListeners($services, $eventManager, $listeners)
265
    {
266 12
        $lazyListeners = [];
267
268 12
        foreach ($listeners as $name => $options) {
269 12
            $options = $this->normalizeListenerOptions($name, $options);
270
271 12
            if ($options['lazy'] && null !== $options['attach']) {
272 7
                foreach ($options['attach'] as $spec) {
273 7
                    $lazyListeners[] = [
274 7
                        'service' => $options['service'],
275 7
                        'event' => $spec['event'],
276 7
                        'method' => $spec['method'],
277 7
                        'priority' => $spec['priority'],
278
                    ];
279
                }
280 7
                continue;
281
            }
282
283 7
            if ($services->has($options['service'])) {
284 5
                $listener = $services->get($options['service']);
285 2
            } elseif (class_exists($options['service'], true)) {
286 1
                $listener = new $options['service']();
287
            } else {
288 1
                throw new \UnexpectedValueException(sprintf(
289 1
                                                        'Class or service %s does not exists. Cannot create listener instance.',
290 1
                    $options['service']
291
                                                    ));
292
            }
293
294 6
            if ($listener instanceof ListenerAggregateInterface) {
295 1
                $listener->attach($eventManager, $options['priority']);
296 1
                continue;
297
            }
298
299 5
            foreach ($options['attach'] as $spec) {
300 5
                $callback = $spec['method'] ? [ $listener, $spec['method'] ] : $listener;
301 5
                $eventManager->attach($spec['event'], $callback, $spec['priority']);
0 ignored issues
show
Bug introduced by
It seems like $callback can also be of type object; however, parameter $listener of Laminas\EventManager\Eve...agerInterface::attach() does only seem to accept callable, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

301
                $eventManager->attach($spec['event'], /** @scrutinizer ignore-type */ $callback, $spec['priority']);
Loading history...
302
            }
303
        }
304
305 11
        if (!empty($lazyListeners)) {
306
            /* @var \Core\Listener\DeferredListenerAggregate $aggregate */
307 7
            $aggregate = $services->get('Core/Listener/DeferredListenerAggregate');
308 7
            $aggregate->setListeners($lazyListeners)
309 7
                      ->attach($eventManager);
310
        }
311
    }
312
313
    /**
314
     * Normalizes the listener configuration.
315
     *
316
     * Converts the options given in the main config file to an array
317
     * containing key => value pairs for easier consumption in
318
     * {@link attachListeners()}
319
     *
320
     * @param int|string $name Service or class name of the listener. (if int, we have config for an aggregate)
321
     * @param string|array $options String is either event name or aggregate name (when name is int).
322
     *                              Array are the options from config. [ [event,..], method, priority, lazy]
323
     *
324
     * @return array
325
     */
326 12
    protected function normalizeListenerOptions($name, $options)
327
    {
328
329
        /*
330
         * $options is an array with following meta-syntax:
331
         *
332
         *  $options = [
333
         *      string:listener => string:event,
334
         *      string:listener => [ string|array:event{, string:methodName}{, int:priority}{, bool:lazy }],
335
         *      string:aggregate, // implies integer value as $name
336
         *      string:aggregate => int:priority,
337
         *      string:listener => [
338
         *          'events' => [ 'event', 'event' => priority, 'event' => 'method',
339
         *                        'event' => [ 'method' => method, 'priority' => priority ],
340
         *                        'event' => [ 'method' => [ 'method', 'method' => priority ], 'priority' => priority ]
341
         *                     ],
342
         *          'method' => method,
343
         *          'priority' => priority,
344
         *          'lazy' => bool
345
         * ]
346
         */
347
348
        $normalized = [
349 12
            'service' => $name,
350
            'attach' => null,
351 12
            'priority' => 1,
352
            'lazy' => false,
353
        ];
354
355 12
        if (is_int($name)) {
356
            /* $options must be the name of an aggregate service or class. */
357 1
            $normalized['service'] = $options;
358 1
            return $normalized;
359
        }
360
361 12
        if (is_int($options)) {
0 ignored issues
show
introduced by
The condition is_int($options) is always false.
Loading history...
362
            /* $name must be the name of an aggregate and the priority is passed. */
363 1
            $normalized['priority'] = $options;
364 1
            return $normalized;
365
        }
366
367 11
        if (is_string($options)) {
368
            /* Only an event name is provided in config */
369 1
            $normalized['attach'] = [ [ 'event' => $options, 'method' => null, 'priority' => 1 ] ];
370 1
            return $normalized;
371
        }
372
373 10
        if (ArrayUtils::isHashTable($options)) {
374 1
            $normalized['attach'] = $this->normalizeEventsSpec($options);
375
376 1
            if (isset($options['lazy'])) {
377 1
                $normalized['lazy'] = $options['lazy'];
378
            }
379
380 1
            return $normalized;
381
        }
382
383 9
        $event = $method = null;
384 9
        $priority = 1;
385 9
        $lazy = false;
386
387 9
        foreach ($options as $opt) {
388 9
            if (is_array($opt)) {
389
                /* Must be event names */
390 2
                $event = $opt;
391 9
            } elseif (is_string($opt)) {
392 9
                if (null === $event) {
393
                    /* first string found is assumed to be the event name */
394 9
                    $event = [ $opt ];
395
                } else {
396
                    /* second string found must be a method name. */
397 9
                    $method = $opt;
398
                }
399 9
            } elseif (is_int($opt)) {
400
                /* Integer values must be priority */
401 5
                $priority = $opt;
402 6
            } elseif (is_bool($opt)) {
403
                /* Lazy option is passed. */
404 6
                $lazy = $opt;
405
            }
406
        }
407
408 9
        foreach ($event as &$eventSpec) {
409 9
            $eventSpec = [ 'event' => $eventSpec, 'method' => $method, 'priority' => $priority ];
410
        }
411
412 9
        $normalized['attach'] = $event;
413 9
        $normalized['lazy']   = $lazy;
414
415 9
        return $normalized;
416
    }
417
418 1
    protected function normalizeEventsSpec($options)
419
    {
420 1
        $listenerPriority = isset($options['priority']) ? $options['priority'] : 1;
421 1
        $listenerMethod   = isset($options['method'])   ? $options['method']   : '__none__';
422 1
        $events = [];
423
424 1
        foreach ($options['events'] as $event => $spec) {
425 1
            $eventPriority = isset($spec['priority']) ? $spec['priority'] : $listenerPriority;
426
427 1
            if (is_int($event)) {
428 1
                $events[$listenerMethod][$eventPriority][] = $spec;
429 1
            } elseif (is_int($spec)) {
430 1
                $events[$listenerMethod][$spec][] = $event;
431 1
            } elseif (is_string($spec)) {
432 1
                $events[$spec][$eventPriority][] = $event;
433 1
            } elseif (is_array($spec)) {
434 1
                if (isset($spec['method'])) {
435 1
                    if (!is_array($spec['method'])) {
436 1
                        $spec['method'] = [ $spec['method'] ];
437
                    }
438
439 1
                    foreach ($spec['method'] as $method => $methodPriority) {
440 1
                        if (is_int($method)) {
441 1
                            $events[$methodPriority][$eventPriority][] = $event;
442 1
                        } elseif (is_int($methodPriority)) {
443 1
                            $events[$method][$methodPriority][] = $event;
444
                        }
445
                    }
446
                }
447
            }
448
        }
449
450 1
        $eventsSpec = [];
451 1
        foreach ($events as $method => $priorities) {
452 1
            foreach ($priorities as $priority => $event) {
453 1
                foreach ($event as $ev) {
454 1
                    $eventsSpec[] = [
455 1
                        'event'    => $ev,
456 1
                        'method'   => '__none__' == $method ? null : $method,
457 1
                        'priority' => $priority,
458
                    ];
459
                }
460
            }
461
        }
462
463 1
        return $eventsSpec;
464
    }
465
}
466