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