Completed
Push — master ( 67a92e...b03a00 )
by Grégoire
01:54
created

SonataNotificationExtension.php (2 issues)

a variable is defined regardless of execution path.

Bug Major

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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