Completed
Push — master ( 166fc0...765fa7 )
by
unknown
13:29
created

SonataNotificationExtension::configureRabbitmq()   C

Complexity

Conditions 12
Paths 98

Size

Total Lines 88
Code Lines 56

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 88
rs 5.034
c 0
b 0
f 0
cc 12
eloc 56
nc 98
nop 2

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