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

SonataNotificationExtension::configureClass()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 10
c 0
b 0
f 0
cc 1
nc 1
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\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