Completed
Push — master ( a68100...c6d2e9 )
by
unknown
17:51
created

SonataNotificationExtension   C

Complexity

Total Complexity 60

Size/Duplication

Total Lines 459
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Importance

Changes 0
Metric Value
wmc 60
lcom 1
cbo 8
dl 0
loc 459
rs 6.0975
c 0
b 0
f 0

14 Methods

Rating   Name   Duplication   Size   Complexity  
B load() 0 54 8
A configureClass() 0 8 1
A configureAdmin() 0 6 1
A registerParameters() 0 5 1
B configureBackends() 0 29 5
A registerDoctrineMapping() 0 12 1
B checkConfiguration() 0 14 7
A configureListeners() 0 19 3
C configureDoctrineBackends() 0 65 13
A createDoctrineQueueBackend() 0 15 2
C configureRabbitmq() 0 90 12
A createAMQPBackend() 0 21 1
A getQueuesParameters() 0 13 2
A getAMQPDeadLetterExchangeByRoutingKey() 0 8 3

How to fix   Complexity   

Complex Class

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

1
<?php
2
3
/*
4
 * This file is part of the Sonata Project package.
5
 *
6
 * (c) Thomas Rabaix <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Sonata\NotificationBundle\DependencyInjection;
13
14
use Sonata\EasyExtendsBundle\Mapper\DoctrineCollector;
15
use Sonata\NotificationBundle\Model\MessageInterface;
16
use Symfony\Component\Config\FileLocator;
17
use Symfony\Component\DependencyInjection\ContainerBuilder;
18
use Symfony\Component\DependencyInjection\Definition;
19
use Symfony\Component\DependencyInjection\Loader;
20
use Symfony\Component\DependencyInjection\Reference;
21
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
22
23
class SonataNotificationExtension extends Extension
24
{
25
    /**
26
     * @var int
27
     */
28
    protected $amqpCounter = 0;
29
30
    /**
31
     * {@inheritdoc}
32
     */
33
    public function load(array $configs, ContainerBuilder $container)
34
    {
35
        $configuration = new Configuration();
36
        $config = $this->processConfiguration($configuration, $configs);
37
38
        $loader = new Loader\XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
39
40
        /*
41
         * NEXT_MAJOR: Remove the check for ServiceClosureArgument as well as core_legacy.xml.
42
         */
43
        if (class_exists('Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument')) {
44
            $loader->load('core.xml');
45
        } else {
46
            $loader->load('core_legacy.xml');
47
        }
48
49
        $loader->load('doctrine_orm.xml');
50
        $loader->load('backend.xml');
51
        $loader->load('consumer.xml');
52
        $loader->load('selector.xml');
53
        $loader->load('event.xml');
54
55
        if ($config['consumers']['register_default']) {
56
            $loader->load('default_consumers.xml');
57
        }
58
59
        $bundles = $container->getParameter('kernel.bundles');
60
61
        if (isset($bundles['FOSRestBundle']) && isset($bundles['NelmioApiDocBundle'])) {
62
            $loader->load('api_controllers.xml');
63
            $loader->load('api_form.xml');
64
        }
65
66
        // for now, only support for ORM
67
        if ($config['admin']['enabled'] && isset($bundles['SonataDoctrineORMAdminBundle'])) {
68
            $loader->load('admin.xml');
69
        }
70
71
        if (isset($bundles['LiipMonitorBundle'])) {
72
            $loader->load('checkmonitor.xml');
73
        }
74
75
        $this->checkConfiguration($config);
76
77
        $container->setAlias('sonata.notification.backend', $config['backend']);
78
        $container->setParameter('sonata.notification.backend', $config['backend']);
79
80
        $this->registerDoctrineMapping($config);
81
        $this->registerParameters($container, $config);
82
        $this->configureBackends($container, $config);
83
        $this->configureClass($container, $config);
84
        $this->configureListeners($container, $config);
85
        $this->configureAdmin($container, $config);
86
    }
87
88
    /**
89
     * @param ContainerBuilder $container
90
     * @param array            $config
91
     */
92
    public function configureClass(ContainerBuilder $container, $config)
93
    {
94
        // admin configuration
95
        $container->setParameter('sonata.notification.admin.message.entity', $config['class']['message']);
96
97
        // manager configuration
98
        $container->setParameter('sonata.notification.manager.message.entity', $config['class']['message']);
99
    }
100
101
    /**
102
     * @param ContainerBuilder $container
103
     * @param array            $config
104
     */
105
    public function configureAdmin(ContainerBuilder $container, $config)
106
    {
107
        $container->setParameter('sonata.notification.admin.message.class', $config['admin']['message']['class']);
108
        $container->setParameter('sonata.notification.admin.message.controller', $config['admin']['message']['controller']);
109
        $container->setParameter('sonata.notification.admin.message.translation_domain', $config['admin']['message']['translation']);
110
    }
111
112
    /**
113
     * @param ContainerBuilder $container
114
     * @param array            $config
115
     */
116
    public function registerParameters(ContainerBuilder $container, $config)
117
    {
118
        $container->setParameter('sonata.notification.message.class', $config['class']['message']);
119
        $container->setParameter('sonata.notification.admin.message.entity', $config['class']['message']);
120
    }
121
122
    /**
123
     * @param ContainerBuilder $container
124
     * @param array            $config
125
     */
126
    public function configureBackends(ContainerBuilder $container, $config)
127
    {
128
        // set the default value, will be erase if required
129
        $container->setAlias('sonata.notification.manager.message', 'sonata.notification.manager.message.default');
130
131
        if (isset($config['backends']['rabbitmq']) && $config['backend'] === 'sonata.notification.backend.rabbitmq') {
132
            $this->configureRabbitmq($container, $config);
133
134
            $container->removeDefinition('sonata.notification.backend.doctrine');
135
        } else {
136
            $container->removeDefinition('sonata.notification.backend.rabbitmq');
137
        }
138
139
        if (isset($config['backends']['doctrine']) && $config['backend'] === 'sonata.notification.backend.doctrine') {
140
            $checkLevel = array(
141
                MessageInterface::STATE_DONE => $config['backends']['doctrine']['states']['done'],
142
                MessageInterface::STATE_ERROR => $config['backends']['doctrine']['states']['error'],
143
                MessageInterface::STATE_IN_PROGRESS => $config['backends']['doctrine']['states']['in_progress'],
144
                MessageInterface::STATE_OPEN => $config['backends']['doctrine']['states']['open'],
145
            );
146
147
            $pause = $config['backends']['doctrine']['pause'];
148
            $maxAge = $config['backends']['doctrine']['max_age'];
149
            $batchSize = $config['backends']['doctrine']['batch_size'];
150
            $container->setAlias('sonata.notification.manager.message', $config['backends']['doctrine']['message_manager']);
151
152
            $this->configureDoctrineBackends($container, $config, $checkLevel, $pause, $maxAge, $batchSize);
0 ignored issues
show
Documentation introduced by
$checkLevel is of type array, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
153
        }
154
    }
155
156
    /**
157
     * @param array $config
158
     */
159
    public function registerDoctrineMapping(array $config)
160
    {
161
        $collector = DoctrineCollector::getInstance();
162
163
        $collector->addIndex($config['class']['message'], 'idx_state', array(
164
            'state',
165
        ));
166
167
        $collector->addIndex($config['class']['message'], 'idx_created_at', array(
168
            'created_at',
169
        ));
170
    }
171
172
    /**
173
     * @param array $config
174
     */
175
    protected function checkConfiguration(array $config)
176
    {
177
        if (isset($config['backends']) && count($config['backends']) > 1) {
178
            throw new \RuntimeException('more than one backend configured, you can have only one backend configuration');
179
        }
180
181
        if (!isset($config['backends']['rabbitmq']) && $config['backend'] === 'sonata.notification.backend.rabbitmq') {
182
            throw new \RuntimeException('Please configure the sonata_notification.backends.rabbitmq section');
183
        }
184
185
        if (!isset($config['backends']['doctrine']) && $config['backend'] === 'sonata.notification.backend.doctrine') {
186
            throw new \RuntimeException('Please configure the sonata_notification.backends.doctrine section');
187
        }
188
    }
189
190
    /**
191
     * @param ContainerBuilder $container
192
     * @param array            $config
193
     */
194
    protected function configureListeners(ContainerBuilder $container, array $config)
195
    {
196
        $ids = $config['iteration_listeners'];
197
198
        // this one clean the unit of work after every iteration
199
        // it must be set on any backend ...
200
        $ids[] = 'sonata.notification.event.doctrine_optimize';
201
202
        if (isset($config['backends']['doctrine']) && $config['backends']['doctrine']['batch_size'] > 1) {
203
            // if the backend is doctrine and the batch size > 1, then
204
            // the unit of work must be cleaned wisely to avoid any issue
205
            // while persisting entities
206
            $ids = array(
207
                'sonata.notification.event.doctrine_backend_optimize',
208
            );
209
        }
210
211
        $container->setParameter('sonata.notification.event.iteration_listeners', $ids);
212
    }
213
214
    /**
215
     * @param ContainerBuilder $container
216
     * @param array            $config
217
     * @param bool             $checkLevel
218
     * @param int              $pause
219
     * @param int              $maxAge
220
     * @param int              $batchSize
221
     *
222
     * @throws \RuntimeException
223
     */
224
    protected function configureDoctrineBackends(ContainerBuilder $container, array $config, $checkLevel, $pause, $maxAge, $batchSize)
225
    {
226
        $queues = $config['queues'];
227
        $qBackends = array();
228
229
        $definition = $container->getDefinition('sonata.notification.backend.doctrine');
230
231
        // no queue defined, set a default one
232
        if (count($queues) == 0) {
233
            $queues = array(array(
234
                'queue' => 'default',
235
                'default' => true,
236
                'types' => array(),
237
            ));
238
        }
239
240
        $defaultSet = false;
241
        $declaredQueues = array();
242
243
        foreach ($queues as $pos => &$queue) {
244
            if (in_array($queue['queue'], $declaredQueues)) {
245
                throw new \RuntimeException('The doctrine backend does not support 2 identicals queue name, please rename one queue');
246
            }
247
248
            $declaredQueues[] = $queue['queue'];
249
250
            // make the configuration compatible with old code and rabbitmq
251
            if (isset($queue['routing_key']) && strlen($queue['routing_key']) > 0) {
252
                $queue['types'] = array($queue['routing_key']);
253
            }
254
255
            if (empty($queue['types']) && $queue['default'] === false) {
256
                throw new \RuntimeException('You cannot declared a doctrine queue with no type defined with default = false');
257
            }
258
259
            if (!empty($queue['types']) && $queue['default'] === true) {
260
                throw new \RuntimeException('You cannot declared a doctrine queue with types defined with default = true');
261
            }
262
263
            $id = $this->createDoctrineQueueBackend($container, $definition->getArgument(0), $checkLevel, $pause, $maxAge, $batchSize, $queue['queue'], $queue['types']);
264
            $qBackends[$pos] = array(
265
                'types' => $queue['types'],
266
                'backend' => new Reference($id),
267
            );
268
269
            if ($queue['default'] === true) {
270
                if ($defaultSet === true) {
271
                    throw new \RuntimeException('You can only set one doctrine default queue in your sonata notification configuration.');
272
                }
273
274
                $defaultSet = true;
275
                $defaultQueue = $queue['queue'];
276
            }
277
        }
278
279
        if ($defaultSet === false) {
280
            throw new \RuntimeException('You need to specify a valid default queue for the doctrine backend!');
281
        }
282
283
        $definition
284
            ->replaceArgument(1, $queues)
285
            ->replaceArgument(2, $defaultQueue)
0 ignored issues
show
Bug introduced by
The variable $defaultQueue does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
286
            ->replaceArgument(3, $qBackends)
287
        ;
288
    }
289
290
    /**
291
     * @param ContainerBuilder $container
292
     * @param string           $manager
293
     * @param bool             $checkLevel
294
     * @param int              $pause
295
     * @param int              $maxAge
296
     * @param int              $batchSize
297
     * @param string           $key
298
     * @param array            $types
299
     *
300
     * @return string
301
     */
302
    protected function createDoctrineQueueBackend(ContainerBuilder $container, $manager, $checkLevel, $pause, $maxAge, $batchSize, $key, array $types = array())
303
    {
304
        if ($key == '') {
305
            $id = 'sonata.notification.backend.doctrine.default_'.$this->amqpCounter++;
306
        } else {
307
            $id = 'sonata.notification.backend.doctrine.'.$key;
308
        }
309
310
        $definition = new Definition('Sonata\NotificationBundle\Backend\MessageManagerBackend', array($manager, $checkLevel, $pause, $maxAge, $batchSize, $types));
311
        $definition->setPublic(false);
312
313
        $container->setDefinition($id, $definition);
314
315
        return $id;
316
    }
317
318
    /**
319
     * @param ContainerBuilder $container
320
     * @param array            $config
321
     */
322
    protected function configureRabbitmq(ContainerBuilder $container, array $config)
323
    {
324
        $queues = $config['queues'];
325
        $connection = $config['backends']['rabbitmq']['connection'];
326
        $baseExchange = $config['backends']['rabbitmq']['exchange'];
327
        $amqBackends = array();
328
329
        if (count($queues) == 0) {
330
            $queues = array(array(
331
                'queue' => 'default',
332
                'default' => true,
333
                'routing_key' => '',
334
                'recover' => false,
335
                'dead_letter_exchange' => null,
336
                'dead_letter_routing_key' => null,
337
                'ttl' => null,
338
            ));
339
        }
340
341
        $deadLetterRoutingKeys = $this->getQueuesParameters('dead_letter_routing_key', $queues);
342
        $routingKeys = $this->getQueuesParameters('routing_key', $queues);
343
344
        foreach ($deadLetterRoutingKeys as $key) {
345
            if (!in_array($key, $routingKeys)) {
346
                throw new \RuntimeException(sprintf(
347
                    'You must configure the queue having the routing_key "%s" same as dead_letter_routing_key', $key
348
                ));
349
            }
350
        }
351
352
        $declaredQueues = array();
353
354
        $defaultSet = false;
355
        foreach ($queues as $pos => $queue) {
356
            if (in_array($queue['queue'], $declaredQueues)) {
357
                throw new \RuntimeException('The RabbitMQ backend does not support 2 identicals queue name, please rename one queue');
358
            }
359
360
            $declaredQueues[] = $queue['queue'];
361
362
            if ($queue['dead_letter_routing_key']) {
363
                if (is_null($queue['dead_letter_exchange'])) {
364
                    throw new \RuntimeException(
365
                        'dead_letter_exchange must be configured when dead_letter_routing_key is set'
366
                    );
367
                }
368
            }
369
370
            if (in_array($queue['routing_key'], $deadLetterRoutingKeys)) {
371
                $exchange = $this->getAMQPDeadLetterExchangeByRoutingKey($queue['routing_key'], $queues);
372
            } else {
373
                $exchange = $baseExchange;
374
            }
375
376
            $id = $this->createAMQPBackend(
377
                $container,
378
                $exchange,
379
                $queue['queue'],
380
                $queue['recover'],
381
                $queue['routing_key'],
382
                $queue['dead_letter_exchange'],
383
                $queue['dead_letter_routing_key'],
384
                $queue['ttl']
385
            );
386
387
            $amqBackends[$pos] = array(
388
                'type' => $queue['routing_key'],
389
                'backend' => new Reference($id),
390
            );
391
392
            if ($queue['default'] === true) {
393
                if ($defaultSet === true) {
394
                    throw new \RuntimeException('You can only set one rabbitmq default queue in your sonata notification configuration.');
395
                }
396
                $defaultSet = true;
397
                $defaultQueue = $queue['routing_key'];
398
            }
399
        }
400
401
        if ($defaultSet === false) {
402
            throw new \RuntimeException('You need to specify a valid default queue for the rabbitmq backend!');
403
        }
404
405
        $container->getDefinition('sonata.notification.backend.rabbitmq')
406
            ->replaceArgument(0, $connection)
407
            ->replaceArgument(1, $queues)
408
            ->replaceArgument(2, $defaultQueue)
0 ignored issues
show
Bug introduced by
The variable $defaultQueue does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
409
            ->replaceArgument(3, $amqBackends)
410
        ;
411
    }
412
413
    /**
414
     * @param ContainerBuilder $container
415
     * @param string           $exchange
416
     * @param string           $name
417
     * @param string           $recover
418
     * @param string           $key
419
     * @param string           $deadLetterExchange
420
     * @param string           $deadLetterRoutingKey
421
     * @param int|null         $ttl
422
     *
423
     * @return string
424
     */
425
    protected function createAMQPBackend(ContainerBuilder $container, $exchange, $name, $recover, $key = '', $deadLetterExchange = null, $deadLetterRoutingKey = null, $ttl = null)
426
    {
427
        $id = 'sonata.notification.backend.rabbitmq.'.$this->amqpCounter++;
428
429
        $definition = new Definition(
430
            'Sonata\NotificationBundle\Backend\AMQPBackend',
431
            array(
432
                $exchange,
433
                $name,
434
                $recover,
435
                $key,
436
                $deadLetterExchange,
437
                $deadLetterRoutingKey,
438
                $ttl,
439
            )
440
        );
441
        $definition->setPublic(false);
442
        $container->setDefinition($id, $definition);
443
444
        return $id;
445
    }
446
447
    /**
448
     * @param string $name
449
     * @param array  $queues
450
     *
451
     * @return string[]
452
     */
453
    private function getQueuesParameters($name, array $queues)
454
    {
455
        $params = array_unique(array_map(function ($q) use ($name) {
456
            return $q[$name];
457
        }, $queues));
458
459
        $idx = array_search(null, $params);
460
        if ($idx !== false) {
461
            unset($params[$idx]);
462
        }
463
464
        return $params;
465
    }
466
467
    /**
468
     * @param string $key
469
     * @param array  $queues
470
     *
471
     * @return string
472
     */
473
    private function getAMQPDeadLetterExchangeByRoutingKey($key, array $queues)
474
    {
475
        foreach ($queues as $queue) {
476
            if ($queue['dead_letter_routing_key'] === $key) {
477
                return $queue['dead_letter_exchange'];
478
            }
479
        }
480
    }
481
}
482