Completed
Pull Request — 3.x (#246)
by
unknown
13:12
created

SonataNotificationExtension   C

Complexity

Total Complexity 63

Size/Duplication

Total Lines 472
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Importance

Changes 0
Metric Value
wmc 63
lcom 1
cbo 8
dl 0
loc 472
rs 5.8893
c 0
b 0
f 0

14 Methods

Rating   Name   Duplication   Size   Complexity  
C load() 0 61 9
A configureClass() 0 8 1
A configureAdmin() 0 6 1
A registerParameters() 0 5 1
C configureBackends() 0 35 7
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
        $loader->load('core.xml');
41
42
        if ('orm' == $config['db_driver']) {
43
            $loader->load('doctrine_orm.xml');
44
        } else {
45
            $loader->load('doctrine_mongodb.xml');
46
        }
47
48
        $loader->load('backend.xml');
49
        $loader->load('consumer.xml');
50
        $loader->load('selector.xml');
51
        $loader->load('event.xml');
52
53
        if ('mongodb' == $config['db_driver']) {
54
            $optimize_definition = $container->getDefinition('sonata.notification.event.doctrine_optimize');
55
            $optimize_definition->replaceArgument(0, new Reference('doctrine_mongodb'));
56
            $backend_optimize_definition = $container->getDefinition('sonata.notification.event.doctrine_backend_optimize');
57
            $backend_optimize_definition->replaceArgument(0, new Reference('doctrine_mongodb'));
58
            $selector_definition = $container->getDefinition('sonata.notification.erroneous_messages_selector');
59
            $selector_definition->replaceArgument(0, new Reference('doctrine_mongodb'));
60
        }
61
62
        if ($config['consumers']['register_default']) {
63
            $loader->load('default_consumers.xml');
64
        }
65
66
        $bundles = $container->getParameter('kernel.bundles');
67
68
        if (isset($bundles['FOSRestBundle']) && isset($bundles['NelmioApiDocBundle'])) {
69
            $loader->load('api_controllers.xml');
70
            $loader->load('api_form.xml');
71
        }
72
73
        // for now, only support for ORM
74
        if ($config['admin']['enabled'] && isset($bundles['SonataDoctrineORMAdminBundle'])) {
75
            $loader->load('admin.xml');
76
        }
77
78
        if (isset($bundles['LiipMonitorBundle'])) {
79
            $loader->load('checkmonitor.xml');
80
        }
81
82
        $this->checkConfiguration($config);
83
84
        $container->setAlias('sonata.notification.backend', $config['backend']);
85
        $container->setParameter('sonata.notification.backend', $config['backend']);
86
87
        $this->registerDoctrineMapping($config);
88
        $this->registerParameters($container, $config);
89
        $this->configureBackends($container, $config);
90
        $this->configureClass($container, $config);
91
        $this->configureListeners($container, $config);
92
        $this->configureAdmin($container, $config);
93
    }
94
95
    /**
96
     * @param ContainerBuilder $container
97
     * @param array            $config
98
     */
99
    public function configureClass(ContainerBuilder $container, $config)
100
    {
101
        // admin configuration
102
        $container->setParameter('sonata.notification.admin.message.entity', $config['class']['message']);
103
104
        // manager configuration
105
        $container->setParameter('sonata.notification.manager.message.entity', $config['class']['message']);
106
    }
107
108
    /**
109
     * @param ContainerBuilder $container
110
     * @param array            $config
111
     */
112
    public function configureAdmin(ContainerBuilder $container, $config)
113
    {
114
        $container->setParameter('sonata.notification.admin.message.class', $config['admin']['message']['class']);
115
        $container->setParameter('sonata.notification.admin.message.controller', $config['admin']['message']['controller']);
116
        $container->setParameter('sonata.notification.admin.message.translation_domain', $config['admin']['message']['translation']);
117
    }
118
119
    /**
120
     * @param ContainerBuilder $container
121
     * @param array            $config
122
     */
123
    public function registerParameters(ContainerBuilder $container, $config)
124
    {
125
        $container->setParameter('sonata.notification.message.class', $config['class']['message']);
126
        $container->setParameter('sonata.notification.admin.message.entity', $config['class']['message']);
127
    }
128
129
    /**
130
     * @param ContainerBuilder $container
131
     * @param array            $config
132
     */
133
    public function configureBackends(ContainerBuilder $container, $config)
134
    {
135
        // set the default value, will be erase if required
136
        if ('orm' == $config['db_driver']) {
137
            $container->setAlias('sonata.notification.manager.message', 'sonata.notification.manager.message.default');
138
        } else {
139
            $container->setAlias('sonata.notification.manager.message', 'sonata.notification.manager.mongodb.message.default');
140
        }
141
142
        if (isset($config['backends']['rabbitmq']) && $config['backend'] === 'sonata.notification.backend.rabbitmq') {
143
            $this->configureRabbitmq($container, $config);
144
145
            $container->removeDefinition('sonata.notification.backend.doctrine');
146
        } else {
147
            $container->removeDefinition('sonata.notification.backend.rabbitmq');
148
        }
149
150
        if (isset($config['backends']['doctrine']) && $config['backend'] === 'sonata.notification.backend.doctrine') {
151
            $checkLevel = array(
152
                MessageInterface::STATE_DONE => $config['backends']['doctrine']['states']['done'],
153
                MessageInterface::STATE_ERROR => $config['backends']['doctrine']['states']['error'],
154
                MessageInterface::STATE_IN_PROGRESS => $config['backends']['doctrine']['states']['in_progress'],
155
                MessageInterface::STATE_OPEN => $config['backends']['doctrine']['states']['open'],
156
            );
157
158
            $pause = $config['backends']['doctrine']['pause'];
159
            $maxAge = $config['backends']['doctrine']['max_age'];
160
            $batchSize = $config['backends']['doctrine']['batch_size'];
161
            if (!is_null($config['backends']['doctrine']['message_manager'])) {
162
                $container->setAlias('sonata.notification.manager.message', $config['backends']['doctrine']['message_manager']);
163
            }
164
165
            $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...
166
        }
167
    }
168
169
    /**
170
     * @param array $config
171
     */
172
    public function registerDoctrineMapping(array $config)
173
    {
174
        $collector = DoctrineCollector::getInstance();
175
176
        $collector->addIndex($config['class']['message'], 'idx_state', array(
177
            'state',
178
        ));
179
180
        $collector->addIndex($config['class']['message'], 'idx_created_at', array(
181
            'created_at',
182
        ));
183
    }
184
185
    /**
186
     * @param array $config
187
     */
188
    protected function checkConfiguration(array $config)
189
    {
190
        if (isset($config['backends']) && count($config['backends']) > 1) {
191
            throw new \RuntimeException('more than one backend configured, you can have only one backend configuration');
192
        }
193
194
        if (!isset($config['backends']['rabbitmq']) && $config['backend'] === 'sonata.notification.backend.rabbitmq') {
195
            throw new \RuntimeException('Please configure the sonata_notification.backends.rabbitmq section');
196
        }
197
198
        if (!isset($config['backends']['doctrine']) && $config['backend'] === 'sonata.notification.backend.doctrine') {
199
            throw new \RuntimeException('Please configure the sonata_notification.backends.doctrine section');
200
        }
201
    }
202
203
    /**
204
     * @param ContainerBuilder $container
205
     * @param array            $config
206
     */
207
    protected function configureListeners(ContainerBuilder $container, array $config)
208
    {
209
        $ids = $config['iteration_listeners'];
210
211
        // this one clean the unit of work after every iteration
212
        // it must be set on any backend ...
213
        $ids[] = 'sonata.notification.event.doctrine_optimize';
214
215
        if (isset($config['backends']['doctrine']) && $config['backends']['doctrine']['batch_size'] > 1) {
216
            // if the backend is doctrine and the batch size > 1, then
217
            // the unit of work must be cleaned wisely to avoid any issue
218
            // while persisting entities
219
            $ids = array(
220
                'sonata.notification.event.doctrine_backend_optimize',
221
            );
222
        }
223
224
        $container->setParameter('sonata.notification.event.iteration_listeners', $ids);
225
    }
226
227
    /**
228
     * @param ContainerBuilder $container
229
     * @param array            $config
230
     * @param bool             $checkLevel
231
     * @param int              $pause
232
     * @param int              $maxAge
233
     * @param int              $batchSize
234
     *
235
     * @throws \RuntimeException
236
     */
237
    protected function configureDoctrineBackends(ContainerBuilder $container, array $config, $checkLevel, $pause, $maxAge, $batchSize)
238
    {
239
        $queues = $config['queues'];
240
        $qBackends = array();
241
242
        $definition = $container->getDefinition('sonata.notification.backend.doctrine');
243
244
        // no queue defined, set a default one
245
        if (count($queues) == 0) {
246
            $queues = array(array(
247
                'queue' => 'default',
248
                'default' => true,
249
                'types' => array(),
250
            ));
251
        }
252
253
        $defaultSet = false;
254
        $declaredQueues = array();
255
256
        foreach ($queues as $pos => &$queue) {
257
            if (in_array($queue['queue'], $declaredQueues)) {
258
                throw new \RuntimeException('The doctrine backend does not support 2 identicals queue name, please rename one queue');
259
            }
260
261
            $declaredQueues[] = $queue['queue'];
262
263
            // make the configuration compatible with old code and rabbitmq
264
            if (isset($queue['routing_key']) && strlen($queue['routing_key']) > 0) {
265
                $queue['types'] = array($queue['routing_key']);
266
            }
267
268
            if (empty($queue['types']) && $queue['default'] === false) {
269
                throw new \RuntimeException('You cannot declared a doctrine queue with no type defined with default = false');
270
            }
271
272
            if (!empty($queue['types']) && $queue['default'] === true) {
273
                throw new \RuntimeException('You cannot declared a doctrine queue with types defined with default = true');
274
            }
275
276
            $id = $this->createDoctrineQueueBackend($container, $definition->getArgument(0), $checkLevel, $pause, $maxAge, $batchSize, $queue['queue'], $queue['types']);
277
            $qBackends[$pos] = array(
278
                'types' => $queue['types'],
279
                'backend' => new Reference($id),
280
            );
281
282
            if ($queue['default'] === true) {
283
                if ($defaultSet === true) {
284
                    throw new \RuntimeException('You can only set one doctrine default queue in your sonata notification configuration.');
285
                }
286
287
                $defaultSet = true;
288
                $defaultQueue = $queue['queue'];
289
            }
290
        }
291
292
        if ($defaultSet === false) {
293
            throw new \RuntimeException('You need to specify a valid default queue for the doctrine backend!');
294
        }
295
296
        $definition
297
            ->replaceArgument(1, $queues)
298
            ->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...
299
            ->replaceArgument(3, $qBackends)
300
        ;
301
    }
302
303
    /**
304
     * @param ContainerBuilder $container
305
     * @param string           $manager
306
     * @param bool             $checkLevel
307
     * @param int              $pause
308
     * @param int              $maxAge
309
     * @param int              $batchSize
310
     * @param string           $key
311
     * @param array            $types
312
     *
313
     * @return string
314
     */
315
    protected function createDoctrineQueueBackend(ContainerBuilder $container, $manager, $checkLevel, $pause, $maxAge, $batchSize, $key, array $types = array())
316
    {
317
        if ($key == '') {
318
            $id = 'sonata.notification.backend.doctrine.default_'.$this->amqpCounter++;
319
        } else {
320
            $id = 'sonata.notification.backend.doctrine.'.$key;
321
        }
322
323
        $definition = new Definition('Sonata\NotificationBundle\Backend\MessageManagerBackend', array($manager, $checkLevel, $pause, $maxAge, $batchSize, $types));
324
        $definition->setPublic(false);
325
326
        $container->setDefinition($id, $definition);
327
328
        return $id;
329
    }
330
331
    /**
332
     * @param ContainerBuilder $container
333
     * @param array            $config
334
     */
335
    protected function configureRabbitmq(ContainerBuilder $container, array $config)
336
    {
337
        $queues = $config['queues'];
338
        $connection = $config['backends']['rabbitmq']['connection'];
339
        $baseExchange = $config['backends']['rabbitmq']['exchange'];
340
        $amqBackends = array();
341
342
        if (count($queues) == 0) {
343
            $queues = array(array(
344
                'queue' => 'default',
345
                'default' => true,
346
                'routing_key' => '',
347
                'recover' => false,
348
                'dead_letter_exchange' => null,
349
                'dead_letter_routing_key' => null,
350
                'ttl' => null,
351
            ));
352
        }
353
354
        $deadLetterRoutingKeys = $this->getQueuesParameters('dead_letter_routing_key', $queues);
355
        $routingKeys = $this->getQueuesParameters('routing_key', $queues);
356
357
        foreach ($deadLetterRoutingKeys as $key) {
358
            if (!in_array($key, $routingKeys)) {
359
                throw new \RuntimeException(sprintf(
360
                    'You must configure the queue having the routing_key "%s" same as dead_letter_routing_key', $key
361
                ));
362
            }
363
        }
364
365
        $declaredQueues = array();
366
367
        $defaultSet = false;
368
        foreach ($queues as $pos => $queue) {
369
            if (in_array($queue['queue'], $declaredQueues)) {
370
                throw new \RuntimeException('The RabbitMQ backend does not support 2 identicals queue name, please rename one queue');
371
            }
372
373
            $declaredQueues[] = $queue['queue'];
374
375
            if ($queue['dead_letter_routing_key']) {
376
                if (is_null($queue['dead_letter_exchange'])) {
377
                    throw new \RuntimeException(
378
                        'dead_letter_exchange must be configured when dead_letter_routing_key is set'
379
                    );
380
                }
381
            }
382
383
            if (in_array($queue['routing_key'], $deadLetterRoutingKeys)) {
384
                $exchange = $this->getAMQPDeadLetterExchangeByRoutingKey($queue['routing_key'], $queues);
385
            } else {
386
                $exchange = $baseExchange;
387
            }
388
389
            $id = $this->createAMQPBackend(
390
                $container,
391
                $exchange,
392
                $queue['queue'],
393
                $queue['recover'],
394
                $queue['routing_key'],
395
                $queue['dead_letter_exchange'],
396
                $queue['dead_letter_routing_key'],
397
                $queue['ttl']
398
            );
399
400
            $amqBackends[$pos] = array(
401
                'type' => $queue['routing_key'],
402
                'backend' => new Reference($id),
403
            );
404
405
            if ($queue['default'] === true) {
406
                if ($defaultSet === true) {
407
                    throw new \RuntimeException('You can only set one rabbitmq default queue in your sonata notification configuration.');
408
                }
409
                $defaultSet = true;
410
                $defaultQueue = $queue['routing_key'];
411
            }
412
        }
413
414
        if ($defaultSet === false) {
415
            throw new \RuntimeException('You need to specify a valid default queue for the rabbitmq backend!');
416
        }
417
418
        $container->getDefinition('sonata.notification.backend.rabbitmq')
419
            ->replaceArgument(0, $connection)
420
            ->replaceArgument(1, $queues)
421
            ->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...
422
            ->replaceArgument(3, $amqBackends)
423
        ;
424
    }
425
426
    /**
427
     * @param ContainerBuilder $container
428
     * @param string           $exchange
429
     * @param string           $name
430
     * @param string           $recover
431
     * @param string           $key
432
     * @param string           $deadLetterExchange
433
     * @param string           $deadLetterRoutingKey
434
     * @param int|null         $ttl
435
     *
436
     * @return string
437
     */
438
    protected function createAMQPBackend(ContainerBuilder $container, $exchange, $name, $recover, $key = '', $deadLetterExchange = null, $deadLetterRoutingKey = null, $ttl = null)
439
    {
440
        $id = 'sonata.notification.backend.rabbitmq.'.$this->amqpCounter++;
441
442
        $definition = new Definition(
443
            'Sonata\NotificationBundle\Backend\AMQPBackend',
444
            array(
445
                $exchange,
446
                $name,
447
                $recover,
448
                $key,
449
                $deadLetterExchange,
450
                $deadLetterRoutingKey,
451
                $ttl,
452
            )
453
        );
454
        $definition->setPublic(false);
455
        $container->setDefinition($id, $definition);
456
457
        return $id;
458
    }
459
460
    /**
461
     * @param string $name
462
     * @param array  $queues
463
     *
464
     * @return string[]
465
     */
466
    private function getQueuesParameters($name, array $queues)
467
    {
468
        $params = array_unique(array_map(function ($q) use ($name) {
469
            return $q[$name];
470
        }, $queues));
471
472
        $idx = array_search(null, $params);
473
        if ($idx !== false) {
474
            unset($params[$idx]);
475
        }
476
477
        return $params;
478
    }
479
480
    /**
481
     * @param string $key
482
     * @param array  $queues
483
     *
484
     * @return string
485
     */
486
    private function getAMQPDeadLetterExchangeByRoutingKey($key, array $queues)
487
    {
488
        foreach ($queues as $queue) {
489
            if ($queue['dead_letter_routing_key'] === $key) {
490
                return $queue['dead_letter_exchange'];
491
            }
492
        }
493
    }
494
}
495