Completed
Pull Request — 3.x (#396)
by
unknown
20:21
created

SonataNotificationExtension::createAMQPBackend()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 22
rs 9.568
c 0
b 0
f 0
cc 1
nc 1
nop 9

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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