Completed
Push — master ( 5f7026...ebe732 )
by
unknown
23:19 queued 13s
created

SonataNotificationExtension::configureListeners()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 20
rs 9.6
c 0
b 0
f 0
cc 3
nc 3
nop 2
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