Completed
Push — develop ( b8f7b1...cea6ad )
by
unknown
07:05
created

createServiceWithName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 5

Duplication

Lines 9
Ratio 100 %

Importance

Changes 0
Metric Value
dl 9
loc 9
rs 9.6666
c 0
b 0
f 0
cc 1
eloc 5
nc 1
nop 3
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 Zend\EventManager\ListenerAggregateInterface;
17
use Zend\ServiceManager\AbstractFactoryInterface;
18
use Zend\ServiceManager\Exception\ServiceNotCreatedException;
19
use Zend\ServiceManager\Exception\ServiceNotFoundException;
20
use Zend\ServiceManager\ServiceLocatorInterface;
21
use Zend\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 View Code Duplication
    public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
123
    {
124
        $config = $this->getConfig($container, $requestedName);
0 ignored issues
show
Compatibility introduced by
$container of type object<Interop\Container\ContainerInterface> is not a sub-type of object<Zend\ServiceManag...erviceLocatorInterface>. It seems like you assume a child interface of the interface Interop\Container\ContainerInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
125
        $events = $this->createEventManager($container, $config);
0 ignored issues
show
Compatibility introduced by
$container of type object<Interop\Container\ContainerInterface> is not a sub-type of object<Zend\ServiceManag...erviceLocatorInterface>. It seems like you assume a child interface of the interface Interop\Container\ContainerInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
126
127
        $this->attachListeners($container, $events, $config['listeners']);
0 ignored issues
show
Compatibility introduced by
$container of type object<Interop\Container\ContainerInterface> is not a sub-type of object<Zend\ServiceManag...erviceLocatorInterface>. It seems like you assume a child interface of the interface Interop\Container\ContainerInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
128
        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
    public function canCreate(ContainerInterface $container, $requestedName)
0 ignored issues
show
Coding Style introduced by
function canCreate() does not seem to conform to the naming convention (^(?:is|has|should|may|supports)).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
Unused Code introduced by
The parameter $container is not used and could be removed.

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

Loading history...
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
        return 0 === strpos(strrev($requestedName), 'stnevE/');
147
    }
148
149
150
    public function canCreateServiceWithName(ServiceLocatorInterface $serviceLocator, $name, $requestedName)
151
    {
152
        return $this->canCreate($serviceLocator, $requestedName);
153
    }
154
155
    /**
156
     * Creates an event manager and attaches preconfigured listeners.
157
     *
158
     * @param ServiceLocatorInterface $serviceLocator
159
     * @param string                  $name
160
     * @param string                  $requestedName
161
     *
162
     * @return \Zend\EventManager\EventManagerInterface
163
     * @throws \UnexpectedValueException
164
     */
165 View Code Duplication
    public function createServiceWithName(ServiceLocatorInterface $serviceLocator, $name, $requestedName)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
166
    {
167
        $config = $this->getConfig($serviceLocator, $requestedName);
168
        $events = $this->createEventManager($serviceLocator, $config);
169
170
        $this->attachListeners($serviceLocator, $events, $config['listeners']);
171
172
        return $events;
173
    }
174
175
    /**
176
     * Gets configuration for an event manager.
177
     *
178
     * Merges the default config with configuration from the main config,
179
     * if a key $name exists in the array under the "event_manager" array in the main config.
180
     *
181
     * @param ServiceLocatorInterface $services
182
     * @param string $name
183
     *
184
     * @return array
185
     */
186
    protected function getConfig($services, $name)
187
    {
188
        $defaults = [
189
            'service' => 'EventManager',
190
            'configure' => true,
191
            'identifiers' => [ $name ],
192
            'event' => '\Zend\EventManager\Event',
193
            'listeners' => [],
194
        ];
195
196
        $config = $services->get('Config');
197
        $config = isset($config['event_manager'][$name]) ? $config['event_manager'][$name] : [];
198
199
        /*
200
         * array_merge does not work, because the default values for 'identifiers' and 'listeners'
201
         * are arrays and array_merge breaks the structure.
202
         */
203
        $config = array_replace_recursive($defaults, $config);
204
205
        return $config;
206
    }
207
208
    /**
209
     * Creates an event manager instance.
210
     *
211
     * Fetches from the service manager or tries to instantiate direct, if no service
212
     * exists in the service manager.
213
     *
214
     * If the key 'configure' in the config array has the value TRUE (default),
215
     * the event manager instance will get configured. Which means, the event prototype
216
     * will be set (after it is fetched from the service manager or instatiated),
217
     * and the shared event manager will be injected.
218
     *
219
     * @param ServiceLocatorInterface $services
220
     * @param array $config
221
     *
222
     * @return \Zend\EventManager\EventManagerInterface
223
     * @throws \UnexpectedValueException if neither a service exists, nor could a class be found.
224
     */
225
    protected function createEventManager($services, $config)
226
    {
227
        /* @var \Zend\EventManager\EventManagerInterface|\Core\EventManager\EventProviderInterface $events */
228
229 View Code Duplication
        if ($services->has($config['service'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
230
            $events = $services->get($config['service']);
231
232
        } else {
233
            if (!class_exists($config['service'], true)) {
234
                throw new \UnexpectedValueException(sprintf(
235
                    'Class or service %s does not exists. Cannot create event manager instance.', $config['service']
236
                ));
237
            }
238
239
            $events = new $config['service']();
240
        }
241
242
        if (false === $config['configure']) {
243
            return $events;
244
        }
245
246
        $events->setIdentifiers($config['identifiers']);
247
248
        if ($events instanceOf EventProviderInterface || method_exists($events, 'setEventPrototype')) {
249
            /* @var \Zend\EventManager\EventInterface $event */
250
            $event = $services->has($config['event']) ? $services->get($config['event']) : new $config['event']();
251
            $events->setEventPrototype($event);
252
        }
253
        else {
254
            $events->setEventClass($config['event']);
255
        }
256
257
        if ('EventManager' != $config['service'] && method_exists($events, 'setSharedManager') && $services->has('SharedEventManager')) {
258
            /* @var \Zend\EventManager\SharedEventManagerInterface $sharedEvents */
259
            $sharedEvents = $services->get('SharedEventManager');
260
            $events->setSharedManager($sharedEvents);
261
        }
262
263
        return $events;
264
    }
265
266
    /**
267
     * Attaches listeners provided in the config to the event manager instance.
268
     *
269
     * @param ServiceLocatorInterface $services
270
     * @param \Zend\EventManager\EventManagerInterface $eventManager
271
     * @param array $listeners
272
     *
273
     * @throws \UnexpectedValueException if a listener name cannot be fetched as service or be instantiated.
274
     */
275
    protected function attachListeners($services, $eventManager, $listeners)
276
    {
277
        $lazyListeners = [];
278
279
        foreach ($listeners as $name => $options) {
280
            $options = $this->normalizeListenerOptions($name, $options);
281
282
            if ($options['lazy'] && null !== $options['attach'] ) {
283
                foreach ($options['attach'] as $spec) {
284
                    $lazyListeners[] = [
285
                        'service' => $options['service'],
286
                        'event' => $spec['events'],
287
                        'method' => $spec['method'],
288
                        'priority' => $spec['priority'],
289
                    ];
290
                }
291
                continue;
292
            }
293
294 View Code Duplication
            if ($services->has($options['service'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
295
                $listener = $services->get($options['service']);
296
297
            } else if (class_exists($options['service'], true)) {
298
                $listener = new $options['service']();
299
300
            } else {
301
                throw new \UnexpectedValueException(sprintf(
302
                                                        'Class or service %s does not exists. Cannot create listener instance.', $options['service']
303
                                                    ));
304
            }
305
306
            if ($listener instanceOf ListenerAggregateInterface) {
307
                $listener->attach($eventManager, $options['priority']);
0 ignored issues
show
Unused Code introduced by
The call to ListenerAggregateInterface::attach() has too many arguments starting with $options['priority'].

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
308
                continue;
309
            }
310
311
            foreach ($options['attach'] as $spec) {
312
                $callback = $spec['method'] ? [ $listener, $spec['method'] ] : $listener;
313
                $eventManager->attach($spec['events'], $callback, $spec['priority']);
314
            }
315
        }
316
317
        if (!empty($lazyListeners)) {
318
            /* @var \Core\Listener\DeferredListenerAggregate $aggregate */
319
            $aggregate = $services->get('Core/Listener/DeferredListenerAggregate');
320
            $aggregate->setListeners($lazyListeners)
321
                      ->attach($eventManager);
322
        }
323
    }
324
325
    /**
326
     * Normalizes the listener configuration.
327
     *
328
     * Converts the options given in the main config file to an array
329
     * containing key => value pairs for easier consumption in
330
     * {@link attachListeners()}
331
     *
332
     * @param int|string $name Service or class name of the listener. (if int, we have config for an aggregate)
333
     * @param string|array $options String is either event name or aggregate name (when name is int).
334
     *                              Array are the options from config. [ [event,..], method, priority, lazy]
335
     *
336
     * @return array
337
     */
338
    protected function normalizeListenerOptions($name, $options)
339
    {
340
341
        /*
0 ignored issues
show
Unused Code Comprehensibility introduced by
37% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
342
         * $options is an array with following meta-syntax:
343
         *
344
         *  $options = [
345
         *      string:listener => string:event,
346
         *      string:listener => [ string|array:event{, string:methodName}{, int:priority}{, bool:lazy }],
347
         *      string:aggregate, // implies integer value as $name
348
         *      string:aggregate => int:priority,
349
         *      string:listener => [
350
         *          'events' => [ 'event', 'event' => priority, 'event' => 'method',
351
         *                        'event' => [ 'method' => method, 'priority' => priority ],
352
         *                        'event' => [ 'method' => [ 'method', 'method' => priority ], 'priority' => priority ]
353
         *                     ],
354
         *          'method' => method,
355
         *          'priority' => priority,
356
         *          'lazy' => bool
357
         * ]
358
         */
359
360
        $normalized = [
361
            'service' => $name,
362
            'attach' => null,
363
            'priority' => 0,
364
            'lazy' => false,
365
        ];
366
367
        if (is_int($name)) {
368
            /* $options must be the name of an aggregate service or class. */
369
            $normalized['service'] = $options;
370
            return $normalized;
371
372
        }
373
374
        if (is_int($options)) {
375
            /* $name must be the name of an aggregate and the priority is passed. */
376
            $normalized['priority'] = $options;
377
            return $normalized;
378
379
        }
380
381
        if (is_string($options)) {
382
            /* Only an event name is provided in config */
383
            $normalized['attach'] = [ [ 'events' => [ $options ], 'method' => null, 'priority' => 0 ] ];
384
            return $normalized;
385
386
        }
387
388
        if (ArrayUtils::isHashTable($options)) {
389
            $normalized['attach'] = $this->normalizeEventsSpec($options);
390
391
            if (isset($options['lazy'])) {
392
                $normalized['lazy'] = $options['lazy'];
393
            }
394
395
            return $normalized;
396
397
        }
398
399
        $event = $method = null;
400
        $priority = 0;
401
        $lazy = false;
402
403
        foreach ($options as $opt) {
404
405
            if (is_array($opt)) {
406
                /* Must be event names */
407
                $event = $opt;
408
409
            } else if (is_string($opt)) {
410
                if (null === $event) {
411
                    /* first string found is assumed to be the event name */
412
                    $event = [ $opt ];
413
                } else {
414
                    /* second string found must be a method name. */
415
                    $method = $opt;
416
                }
417
418
            } else if (is_int($opt)) {
419
                /* Integer values must be priority */
420
                $priority = $opt;
421
422
            } else if (is_bool($opt)) {
423
                /* Lazy option is passed. */
424
                $lazy = $opt;
425
            }
426
        }
427
428
        $normalized['attach'] = [ [ 'events' => $event, 'method' => $method, 'priority' => $priority ] ];
429
        $normalized['lazy']   = $lazy;
430
431
        return $normalized;
432
    }
433
434
    protected function normalizeEventsSpec($options)
435
    {
436
        $listenerPriority = isset($options['priority']) ? $options['priority'] : 0;
437
        $listenerMethod   = isset($options['method'])   ? $options['method']   : '__none__';
438
        $events = [];
439
440
        foreach ($options['events'] as $event => $spec) {
441
442
443
            $eventPriority = isset($spec['priority']) ? $spec['priority'] : $listenerPriority;
444
445
            if (is_int($event)) {
446
                $events[$listenerMethod][$eventPriority][] = $spec;
447
448
            } else if (is_int($spec)) {
449
                $events[$listenerMethod][$spec][] = $event;
450
451
            } else if (is_string($spec)) {
452
                $events[$spec][$eventPriority][] = $event;
453
454
            } else if (is_array($spec)) {
455
                if (isset($spec['method'])) {
456
                    if (!is_array($spec['method'])) {
457
                        $spec['method'] = [ $spec['method'] ];
458
                    }
459
460
                    foreach ($spec['method'] as $method => $methodPriority) {
461
                        if (is_int($method)) {
462
                            $events[$methodPriority][$eventPriority][] = $event;
463
464
                        } else if (is_int($methodPriority)) {
465
                            $events[$method][$methodPriority][] = $event;
466
                        }
467
                    }
468
                }
469
            }
470
        }
471
472
        $eventsSpec = [];
473
        foreach ($events as $method => $priorities) {
474
            foreach ($priorities as $priority => $event) {
475
                $eventsSpec[] = [
476
                    'events' => $event,
477
                    'method' => '__none__' == $method ? null : $method,
478
                    'priority' => $priority,
479
                ];
480
            }
481
        }
482
483
        return $eventsSpec;
484
    }
485
}