Completed
Push — master ( 26c16f...bda5f5 )
by Andreas
01:51
created

Configuration::getCommentedParamDeprecationMsg()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 14
rs 9.7998
c 0
b 0
f 0
cc 2
nc 2
nop 0
1
<?php
2
3
namespace Doctrine\Bundle\DoctrineBundle\DependencyInjection;
4
5
use Doctrine\ORM\EntityManager;
6
use ReflectionClass;
7
use Symfony\Component\Config\Definition\BaseNode;
8
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
9
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
10
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
11
use Symfony\Component\Config\Definition\ConfigurationInterface;
12
use Symfony\Component\DependencyInjection\Exception\LogicException;
13
use function array_key_exists;
14
use function in_array;
15
use function is_array;
16
17
/**
18
 * This class contains the configuration information for the bundle
19
 *
20
 * This information is solely responsible for how the different configuration
21
 * sections are normalized, and merged.
22
 */
23
class Configuration implements ConfigurationInterface
24
{
25
    /** @var bool */
26
    private $debug;
27
28
    /**
29
     * @param bool $debug Whether to use the debug mode
30
     */
31
    public function __construct($debug)
32
    {
33
        $this->debug = (bool) $debug;
34
    }
35
36
    /**
37
     * {@inheritDoc}
38
     */
39
    public function getConfigTreeBuilder() : TreeBuilder
40
    {
41
        $treeBuilder = new TreeBuilder('doctrine');
42
        $rootNode    = $treeBuilder->getRootNode();
43
44
        $this->addDbalSection($rootNode);
0 ignored issues
show
Compatibility introduced by
$rootNode of type object<Symfony\Component...Builder\NodeDefinition> is not a sub-type of object<Symfony\Component...er\ArrayNodeDefinition>. It seems like you assume a child class of the class Symfony\Component\Config...\Builder\NodeDefinition to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
45
        $this->addOrmSection($rootNode);
0 ignored issues
show
Compatibility introduced by
$rootNode of type object<Symfony\Component...Builder\NodeDefinition> is not a sub-type of object<Symfony\Component...er\ArrayNodeDefinition>. It seems like you assume a child class of the class Symfony\Component\Config...\Builder\NodeDefinition to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
46
47
        return $treeBuilder;
48
    }
49
50
    /**
51
     * Add DBAL section to configuration tree
52
     */
53
    private function addDbalSection(ArrayNodeDefinition $node) : void
54
    {
55
        $node
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Symfony\Component\Config...\Builder\NodeDefinition as the method children() does only exist in the following sub-classes of Symfony\Component\Config...\Builder\NodeDefinition: Symfony\Component\Config...der\ArrayNodeDefinition. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
56
            ->children()
57
            ->arrayNode('dbal')
58
                ->beforeNormalization()
59
                    ->ifTrue(static function ($v) {
60
                        return is_array($v) && ! array_key_exists('connections', $v) && ! array_key_exists('connection', $v);
61
                    })
62
                    ->then(static function ($v) {
63
                        // Key that should not be rewritten to the connection config
64
                        $excludedKeys = ['default_connection' => true, 'types' => true, 'type' => true];
65
                        $connection   = [];
66 View Code Duplication
                        foreach ($v as $key => $value) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
67
                            if (isset($excludedKeys[$key])) {
68
                                continue;
69
                            }
70
                            $connection[$key] = $v[$key];
71
                            unset($v[$key]);
72
                        }
73
                        $v['default_connection'] = isset($v['default_connection']) ? (string) $v['default_connection'] : 'default';
74
                        $v['connections']        = [$v['default_connection'] => $connection];
75
76
                        return $v;
77
                    })
78
                ->end()
79
                ->children()
80
                    ->scalarNode('default_connection')->end()
81
                ->end()
82
                ->fixXmlConfig('type')
83
                ->children()
84
                    ->arrayNode('types')
85
                        ->useAttributeAsKey('name')
86
                        ->prototype('array')
87
                            ->beforeNormalization()
88
                                ->ifString()
89
                                ->then(static function ($v) {
90
                                    return ['class' => $v];
91
                                })
92
                            ->end()
93
                            ->children()
94
                                ->scalarNode('class')->isRequired()->end()
95
                                ->booleanNode('commented')->setDeprecated(...$this->getCommentedParamDeprecationMsg())->end()
96
                            ->end()
97
                        ->end()
98
                    ->end()
99
                ->end()
100
                ->fixXmlConfig('connection')
101
                ->append($this->getDbalConnectionsNode())
102
            ->end();
103
    }
104
105
    /**
106
     * Return the dbal connections node
107
     */
108
    private function getDbalConnectionsNode() : ArrayNodeDefinition
109
    {
110
        $treeBuilder = new TreeBuilder('connections');
111
        $node        = $treeBuilder->getRootNode();
112
113
        /** @var ArrayNodeDefinition $connectionNode */
114
        $connectionNode = $node
115
            ->requiresAtLeastOneElement()
116
            ->useAttributeAsKey('name')
117
            ->prototype('array');
118
119
        $this->configureDbalDriverNode($connectionNode);
120
121
        $connectionNode
122
            ->fixXmlConfig('option')
123
            ->fixXmlConfig('mapping_type')
124
            ->fixXmlConfig('slave')
125
            ->fixXmlConfig('shard')
126
            ->fixXmlConfig('default_table_option')
127
            ->children()
128
                ->scalarNode('driver')->defaultValue('pdo_mysql')->end()
129
                ->scalarNode('platform_service')->end()
130
                ->booleanNode('auto_commit')->end()
131
                ->scalarNode('schema_filter')->end()
132
                ->booleanNode('logging')->defaultValue($this->debug)->end()
133
                ->booleanNode('profiling')->defaultValue($this->debug)->end()
134
                ->booleanNode('profiling_collect_backtrace')
135
                    ->defaultValue(false)
136
                    ->info('Enables collecting backtraces when profiling is enabled')
137
                ->end()
138
                ->booleanNode('profiling_collect_schema_errors')
139
                    ->defaultValue(true)
140
                    ->info('Enables collecting schema errors when profiling is enabled')
141
                ->end()
142
                ->scalarNode('server_version')->end()
143
                ->scalarNode('driver_class')->end()
144
                ->scalarNode('wrapper_class')->end()
145
                ->scalarNode('shard_manager_class')->end()
146
                ->scalarNode('shard_choser')->end()
147
                ->scalarNode('shard_choser_service')->end()
148
                ->booleanNode('keep_slave')->end()
149
                ->arrayNode('options')
150
                    ->useAttributeAsKey('key')
151
                    ->prototype('variable')->end()
152
                ->end()
153
                ->arrayNode('mapping_types')
154
                    ->useAttributeAsKey('name')
155
                    ->prototype('scalar')->end()
156
                ->end()
157
                ->arrayNode('default_table_options')
158
                    ->info("This option is used by the schema-tool and affects generated SQL. Possible keys include 'charset','collate', and 'engine'.")
159
                    ->useAttributeAsKey('name')
160
                    ->prototype('scalar')->end()
161
                ->end()
162
            ->end();
163
164
        $slaveNode = $connectionNode
165
            ->children()
166
                ->arrayNode('slaves')
167
                    ->useAttributeAsKey('name')
168
                    ->prototype('array');
169
        $this->configureDbalDriverNode($slaveNode);
0 ignored issues
show
Compatibility introduced by
$slaveNode of type object<Symfony\Component...Builder\NodeDefinition> is not a sub-type of object<Symfony\Component...er\ArrayNodeDefinition>. It seems like you assume a child class of the class Symfony\Component\Config...\Builder\NodeDefinition to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
170
171
        $shardNode = $connectionNode
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Symfony\Component\Config...\Builder\NodeDefinition as the method children() does only exist in the following sub-classes of Symfony\Component\Config...\Builder\NodeDefinition: Symfony\Component\Config...der\ArrayNodeDefinition. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
172
            ->children()
173
                ->arrayNode('shards')
174
                    ->prototype('array')
175
                    ->children()
176
                        ->integerNode('id')
177
                            ->min(1)
178
                            ->isRequired()
179
                        ->end()
180
                    ->end();
181
        $this->configureDbalDriverNode($shardNode);
182
183
        return $node;
184
    }
185
186
    /**
187
     * Adds config keys related to params processed by the DBAL drivers
188
     *
189
     * These keys are available for slave configurations too.
190
     */
191
    private function configureDbalDriverNode(ArrayNodeDefinition $node) : void
192
    {
193
        $node
194
            ->children()
195
                ->scalarNode('url')->info('A URL with connection information; any parameter value parsed from this string will override explicitly set parameters')->end()
196
                ->scalarNode('dbname')->end()
197
                ->scalarNode('host')->defaultValue('localhost')->end()
198
                ->scalarNode('port')->defaultNull()->end()
199
                ->scalarNode('user')->defaultValue('root')->end()
200
                ->scalarNode('password')->defaultNull()->end()
201
                ->scalarNode('application_name')->end()
202
                ->scalarNode('charset')->end()
203
                ->scalarNode('path')->end()
204
                ->booleanNode('memory')->end()
205
                ->scalarNode('unix_socket')->info('The unix socket to use for MySQL')->end()
206
                ->booleanNode('persistent')->info('True to use as persistent connection for the ibm_db2 driver')->end()
207
                ->scalarNode('protocol')->info('The protocol to use for the ibm_db2 driver (default to TCPIP if ommited)')->end()
208
                ->booleanNode('service')
209
                    ->info('True to use SERVICE_NAME as connection parameter instead of SID for Oracle')
210
                ->end()
211
                ->scalarNode('servicename')
212
                    ->info(
213
                        'Overrules dbname parameter if given and used as SERVICE_NAME or SID connection parameter ' .
214
                        'for Oracle depending on the service parameter.'
215
                    )
216
                ->end()
217
                ->scalarNode('sessionMode')
218
                    ->info('The session mode to use for the oci8 driver')
219
                ->end()
220
                ->scalarNode('server')
221
                    ->info('The name of a running database server to connect to for SQL Anywhere.')
222
                ->end()
223
                ->scalarNode('default_dbname')
224
                    ->info(
225
                        'Override the default database (postgres) to connect to for PostgreSQL connexion.'
226
                    )
227
                ->end()
228
                ->scalarNode('sslmode')
229
                    ->info(
230
                        'Determines whether or with what priority a SSL TCP/IP connection will be negotiated with ' .
231
                        'the server for PostgreSQL.'
232
                    )
233
                ->end()
234
                ->scalarNode('sslrootcert')
235
                    ->info(
236
                        'The name of a file containing SSL certificate authority (CA) certificate(s). ' .
237
                        'If the file exists, the server\'s certificate will be verified to be signed by one of these authorities.'
238
                    )
239
                ->end()
240
                ->scalarNode('sslcert')
241
                    ->info(
242
                        'The path to the SSL client certificate file for PostgreSQL.'
243
                    )
244
                ->end()
245
                ->scalarNode('sslkey')
246
                    ->info(
247
                        'The path to the SSL client key file for PostgreSQL.'
248
                    )
249
                ->end()
250
                ->scalarNode('sslcrl')
251
                    ->info(
252
                        'The file name of the SSL certificate revocation list for PostgreSQL.'
253
                    )
254
                ->end()
255
                ->booleanNode('pooled')->info('True to use a pooled server with the oci8/pdo_oracle driver')->end()
256
                ->booleanNode('MultipleActiveResultSets')->info('Configuring MultipleActiveResultSets for the pdo_sqlsrv driver')->end()
257
                ->booleanNode('use_savepoints')->info('Use savepoints for nested transactions')->end()
258
                ->scalarNode('instancename')
259
                ->info(
260
                    'Optional parameter, complete whether to add the INSTANCE_NAME parameter in the connection.' .
261
                    ' It is generally used to connect to an Oracle RAC server to select the name' .
262
                    ' of a particular instance.'
263
                )
264
                ->end()
265
                ->scalarNode('connectstring')
266
                ->info(
267
                    'Complete Easy Connect connection descriptor, see https://docs.oracle.com/database/121/NETAG/naming.htm.' .
268
                    'When using this option, you will still need to provide the user and password parameters, but the other ' .
269
                    'parameters will no longer be used. Note that when using this parameter, the getHost and getPort methods' .
270
                    ' from Doctrine\DBAL\Connection will no longer function as expected.'
271
                )
272
                ->end()
273
            ->end()
274
            ->beforeNormalization()
275
                ->ifTrue(static function ($v) {
276
                    return ! isset($v['sessionMode']) && isset($v['session_mode']);
277
                })
278
                ->then(static function ($v) {
279
                    $v['sessionMode'] = $v['session_mode'];
280
                    unset($v['session_mode']);
281
282
                    return $v;
283
                })
284
            ->end()
285
            ->beforeNormalization()
286
                ->ifTrue(static function ($v) {
287
                    return ! isset($v['MultipleActiveResultSets']) && isset($v['multiple_active_result_sets']);
288
                })
289
                ->then(static function ($v) {
290
                    $v['MultipleActiveResultSets'] = $v['multiple_active_result_sets'];
291
                    unset($v['multiple_active_result_sets']);
292
293
                    return $v;
294
                })
295
            ->end();
296
    }
297
298
    /**
299
     * Add the ORM section to configuration tree
300
     */
301
    private function addOrmSection(ArrayNodeDefinition $node) : void
302
    {
303
        $node
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Symfony\Component\Config...\Builder\NodeDefinition as the method children() does only exist in the following sub-classes of Symfony\Component\Config...\Builder\NodeDefinition: Symfony\Component\Config...der\ArrayNodeDefinition. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
304
            ->children()
305
                ->arrayNode('orm')
306
                    ->beforeNormalization()
307
                        ->ifTrue(static function ($v) {
308
                            if (! empty($v) && ! class_exists(EntityManager::class)) {
309
                                throw new LogicException('The doctrine/orm package is required when the doctrine.orm config is set.');
310
                            }
311
312
                            return $v === null || (is_array($v) && ! array_key_exists('entity_managers', $v) && ! array_key_exists('entity_manager', $v));
313
                        })
314
                        ->then(static function ($v) {
315
                            $v = (array) $v;
316
                            // Key that should not be rewritten to the connection config
317
                            $excludedKeys  = [
318
                                'default_entity_manager' => true,
319
                                'auto_generate_proxy_classes' => true,
320
                                'proxy_dir' => true,
321
                                'proxy_namespace' => true,
322
                                'resolve_target_entities' => true,
323
                                'resolve_target_entity' => true,
324
                            ];
325
                            $entityManager = [];
326 View Code Duplication
                            foreach ($v as $key => $value) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
327
                                if (isset($excludedKeys[$key])) {
328
                                    continue;
329
                                }
330
                                $entityManager[$key] = $v[$key];
331
                                unset($v[$key]);
332
                            }
333
                            $v['default_entity_manager'] = isset($v['default_entity_manager']) ? (string) $v['default_entity_manager'] : 'default';
334
                            $v['entity_managers']        = [$v['default_entity_manager'] => $entityManager];
335
336
                            return $v;
337
                        })
338
                    ->end()
339
                    ->children()
340
                        ->scalarNode('default_entity_manager')->end()
341
                        ->scalarNode('auto_generate_proxy_classes')->defaultValue(false)
342
                            ->info('Auto generate mode possible values are: "NEVER", "ALWAYS", "FILE_NOT_EXISTS", "EVAL"')
343
                            ->validate()
344
                                ->ifTrue(function ($v) {
345
                                    $generationModes = $this->getAutoGenerateModes();
346
347
                                    if (is_int($v) && in_array($v, $generationModes['values']/*array(0, 1, 2, 3)*/)) {
348
                                        return false;
349
                                    }
350
                                    if (is_bool($v)) {
351
                                        return false;
352
                                    }
353
                                    if (is_string($v)) {
354
                                        if (in_array(strtoupper($v), $generationModes['names']/*array('NEVER', 'ALWAYS', 'FILE_NOT_EXISTS', 'EVAL')*/)) {
355
                                            return false;
356
                                        }
357
                                    }
358
359
                                    return true;
360
                                })
361
                                ->thenInvalid('Invalid auto generate mode value %s')
362
                            ->end()
363
                            ->validate()
364
                                ->ifString()
365
                                ->then(static function ($v) {
366
                                    return constant('Doctrine\Common\Proxy\AbstractProxyFactory::AUTOGENERATE_' . strtoupper($v));
367
                                })
368
                            ->end()
369
                        ->end()
370
                        ->scalarNode('proxy_dir')->defaultValue('%kernel.cache_dir%/doctrine/orm/Proxies')->end()
371
                        ->scalarNode('proxy_namespace')->defaultValue('Proxies')->end()
372
                    ->end()
373
                    ->fixXmlConfig('entity_manager')
374
                    ->append($this->getOrmEntityManagersNode())
375
                    ->fixXmlConfig('resolve_target_entity', 'resolve_target_entities')
376
                    ->append($this->getOrmTargetEntityResolverNode())
377
                ->end()
378
            ->end();
379
    }
380
381
    /**
382
     * Return ORM target entity resolver node
383
     */
384
    private function getOrmTargetEntityResolverNode() : NodeDefinition
385
    {
386
        $treeBuilder = new TreeBuilder('resolve_target_entities');
387
        $node        = $treeBuilder->getRootNode();
388
389
        $node
0 ignored issues
show
Bug introduced by
The method useAttributeAsKey() does not exist on Symfony\Component\Config...\Builder\NodeDefinition. Did you maybe mean attribute()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
390
            ->useAttributeAsKey('interface')
391
            ->prototype('scalar')
392
                ->cannotBeEmpty()
393
            ->end();
394
395
        return $node;
396
    }
397
398
    /**
399
     * Return ORM entity listener node
400
     */
401
    private function getOrmEntityListenersNode() : NodeDefinition
402
    {
403
        $treeBuilder = new TreeBuilder('entity_listeners');
404
        $node        = $treeBuilder->getRootNode();
405
406
        $normalizer = static function ($mappings) {
407
            $entities = [];
408
409
            foreach ($mappings as $entityClass => $mapping) {
410
                $listeners = [];
411
412
                foreach ($mapping as $listenerClass => $listenerEvent) {
413
                    $events = [];
414
415
                    foreach ($listenerEvent as $eventType => $eventMapping) {
416
                        if ($eventMapping === null) {
417
                            $eventMapping = [null];
418
                        }
419
420
                        foreach ($eventMapping as $method) {
421
                            $events[] = [
422
                                'type' => $eventType,
423
                                'method' => $method,
424
                            ];
425
                        }
426
                    }
427
428
                    $listeners[] = [
429
                        'class' => $listenerClass,
430
                        'event' => $events,
431
                    ];
432
                }
433
434
                $entities[] = [
435
                    'class' => $entityClass,
436
                    'listener' => $listeners,
437
                ];
438
            }
439
440
            return ['entities' => $entities];
441
        };
442
443
        $node
444
            ->beforeNormalization()
445
                // Yaml normalization
446
                ->ifTrue(static function ($v) {
447
                    return is_array(reset($v)) && is_string(key(reset($v)));
448
                })
449
                ->then($normalizer)
450
            ->end()
451
            ->fixXmlConfig('entity', 'entities')
452
            ->children()
453
                ->arrayNode('entities')
454
                    ->useAttributeAsKey('class')
455
                    ->prototype('array')
456
                        ->fixXmlConfig('listener')
457
                        ->children()
458
                            ->arrayNode('listeners')
459
                                ->useAttributeAsKey('class')
460
                                ->prototype('array')
461
                                    ->fixXmlConfig('event')
462
                                    ->children()
463
                                        ->arrayNode('events')
464
                                            ->prototype('array')
465
                                                ->children()
466
                                                    ->scalarNode('type')->end()
467
                                                    ->scalarNode('method')->defaultNull()->end()
468
                                                ->end()
469
                                            ->end()
470
                                        ->end()
471
                                    ->end()
472
                                ->end()
473
                            ->end()
474
                        ->end()
475
                    ->end()
476
                ->end()
477
            ->end();
478
479
        return $node;
480
    }
481
482
    /**
483
     * Return ORM entity manager node
484
     */
485
    private function getOrmEntityManagersNode() : ArrayNodeDefinition
486
    {
487
        $treeBuilder = new TreeBuilder('entity_managers');
488
        $node        = $treeBuilder->getRootNode();
489
490
        $node
491
            ->requiresAtLeastOneElement()
492
            ->useAttributeAsKey('name')
493
            ->prototype('array')
494
                ->addDefaultsIfNotSet()
495
                ->append($this->getOrmCacheDriverNode('query_cache_driver'))
496
                ->append($this->getOrmCacheDriverNode('metadata_cache_driver'))
497
                ->append($this->getOrmCacheDriverNode('result_cache_driver'))
498
                ->append($this->getOrmEntityListenersNode())
499
                ->children()
500
                    ->scalarNode('connection')->end()
501
                    ->scalarNode('class_metadata_factory_name')->defaultValue('Doctrine\ORM\Mapping\ClassMetadataFactory')->end()
502
                    ->scalarNode('default_repository_class')->defaultValue('Doctrine\ORM\EntityRepository')->end()
503
                    ->scalarNode('auto_mapping')->defaultFalse()->end()
504
                    ->scalarNode('naming_strategy')->defaultValue('doctrine.orm.naming_strategy.default')->end()
505
                    ->scalarNode('quote_strategy')->defaultValue('doctrine.orm.quote_strategy.default')->end()
506
                    ->scalarNode('entity_listener_resolver')->defaultNull()->end()
507
                    ->scalarNode('repository_factory')->defaultValue('doctrine.orm.container_repository_factory')->end()
508
                ->end()
509
                ->children()
510
                    ->arrayNode('second_level_cache')
511
                        ->children()
512
                            ->append($this->getOrmCacheDriverNode('region_cache_driver'))
513
                            ->scalarNode('region_lock_lifetime')->defaultValue(60)->end()
514
                            ->booleanNode('log_enabled')->defaultValue($this->debug)->end()
515
                            ->scalarNode('region_lifetime')->defaultValue(0)->end()
516
                            ->booleanNode('enabled')->defaultValue(true)->end()
517
                            ->scalarNode('factory')->end()
518
                        ->end()
519
                        ->fixXmlConfig('region')
520
                        ->children()
521
                            ->arrayNode('regions')
522
                                ->useAttributeAsKey('name')
523
                                ->prototype('array')
524
                                    ->children()
525
                                        ->append($this->getOrmCacheDriverNode('cache_driver'))
526
                                        ->scalarNode('lock_path')->defaultValue('%kernel.cache_dir%/doctrine/orm/slc/filelock')->end()
527
                                        ->scalarNode('lock_lifetime')->defaultValue(60)->end()
528
                                        ->scalarNode('type')->defaultValue('default')->end()
529
                                        ->scalarNode('lifetime')->defaultValue(0)->end()
530
                                        ->scalarNode('service')->end()
531
                                        ->scalarNode('name')->end()
532
                                    ->end()
533
                                ->end()
534
                            ->end()
535
                        ->end()
536
                        ->fixXmlConfig('logger')
537
                        ->children()
538
                            ->arrayNode('loggers')
539
                                ->useAttributeAsKey('name')
540
                                ->prototype('array')
541
                                    ->children()
542
                                        ->scalarNode('name')->end()
543
                                        ->scalarNode('service')->end()
544
                                    ->end()
545
                                ->end()
546
                            ->end()
547
                        ->end()
548
                    ->end()
549
                ->end()
550
                ->fixXmlConfig('hydrator')
551
                ->children()
552
                    ->arrayNode('hydrators')
553
                        ->useAttributeAsKey('name')
554
                        ->prototype('scalar')->end()
555
                    ->end()
556
                ->end()
557
                ->fixXmlConfig('mapping')
558
                ->children()
559
                    ->arrayNode('mappings')
560
                        ->useAttributeAsKey('name')
561
                        ->prototype('array')
562
                            ->beforeNormalization()
563
                                ->ifString()
564
                                ->then(static function ($v) {
565
                                    return ['type' => $v];
566
                                })
567
                            ->end()
568
                            ->treatNullLike([])
569
                            ->treatFalseLike(['mapping' => false])
570
                            ->performNoDeepMerging()
571
                            ->children()
572
                                ->scalarNode('mapping')->defaultValue(true)->end()
573
                                ->scalarNode('type')->end()
574
                                ->scalarNode('dir')->end()
575
                                ->scalarNode('alias')->end()
576
                                ->scalarNode('prefix')->end()
577
                                ->booleanNode('is_bundle')->end()
578
                            ->end()
579
                        ->end()
580
                    ->end()
581
                    ->arrayNode('dql')
582
                        ->fixXmlConfig('string_function')
583
                        ->fixXmlConfig('numeric_function')
584
                        ->fixXmlConfig('datetime_function')
585
                        ->children()
586
                            ->arrayNode('string_functions')
587
                                ->useAttributeAsKey('name')
588
                                ->prototype('scalar')->end()
589
                            ->end()
590
                            ->arrayNode('numeric_functions')
591
                                ->useAttributeAsKey('name')
592
                                ->prototype('scalar')->end()
593
                            ->end()
594
                            ->arrayNode('datetime_functions')
595
                                ->useAttributeAsKey('name')
596
                                ->prototype('scalar')->end()
597
                            ->end()
598
                        ->end()
599
                    ->end()
600
                ->end()
601
                ->fixXmlConfig('filter')
602
                ->children()
603
                    ->arrayNode('filters')
604
                        ->info('Register SQL Filters in the entity manager')
605
                        ->useAttributeAsKey('name')
606
                        ->prototype('array')
607
                            ->beforeNormalization()
608
                                ->ifString()
609
                                ->then(static function ($v) {
610
                                    return ['class' => $v];
611
                                })
612
                            ->end()
613
                            ->beforeNormalization()
614
                                // The content of the XML node is returned as the "value" key so we need to rename it
615
                                ->ifTrue(static function ($v) {
616
                                    return is_array($v) && isset($v['value']);
617
                                })
618
                                ->then(static function ($v) {
619
                                    $v['class'] = $v['value'];
620
                                    unset($v['value']);
621
622
                                    return $v;
623
                                })
624
                            ->end()
625
                            ->fixXmlConfig('parameter')
626
                            ->children()
627
                                ->scalarNode('class')->isRequired()->end()
628
                                ->booleanNode('enabled')->defaultFalse()->end()
629
                                ->arrayNode('parameters')
630
                                    ->useAttributeAsKey('name')
631
                                    ->prototype('variable')->end()
632
                                ->end()
633
                            ->end()
634
                        ->end()
635
                    ->end()
636
                ->end()
637
            ->end();
638
639
        return $node;
640
    }
641
642
    /**
643
     * Return a ORM cache driver node for an given entity manager
644
     */
645
    private function getOrmCacheDriverNode(string $name) : ArrayNodeDefinition
646
    {
647
        $treeBuilder = new TreeBuilder($name);
648
        $node        = $treeBuilder->getRootNode();
649
650
        $node
651
            ->addDefaultsIfNotSet()
652
            ->beforeNormalization()
653
                ->ifString()
654
                ->then(static function ($v) : array {
655
                    return ['type' => $v];
656
                })
657
            ->end()
658
            ->children()
659
                ->scalarNode('type')->defaultNull()->end()
660
                ->scalarNode('id')->end()
661
                ->scalarNode('pool')->end()
662
            ->end();
663
664
        return $node;
665
    }
666
667
    /**
668
     * Find proxy auto generate modes for their names and int values
669
     */
670
    private function getAutoGenerateModes() : array
671
    {
672
        $constPrefix = 'AUTOGENERATE_';
673
        $prefixLen   = strlen($constPrefix);
674
        $refClass    = new ReflectionClass('Doctrine\Common\Proxy\AbstractProxyFactory');
675
        $constsArray = $refClass->getConstants();
676
        $namesArray  = [];
677
        $valuesArray = [];
678
679
        foreach ($constsArray as $key => $value) {
680
            if (strpos($key, $constPrefix) !== 0) {
681
                continue;
682
            }
683
684
            $namesArray[]  = substr($key, $prefixLen);
685
            $valuesArray[] = (int) $value;
686
        }
687
688
        return [
689
            'names' => $namesArray,
690
            'values' => $valuesArray,
691
        ];
692
    }
693
694
    /**
695
     * Returns the correct deprecation param's as an array for setDeprecated.
696
     *
697
     * Symfony/Config v5.1 introduces a deprecation notice when calling
698
     * setDeprecation() with less than 3 args and the getDeprecation method was
699
     * introduced at the same time. By checking if getDeprecation() exists,
700
     * we can determine the correct param count to use when calling setDeprecated.
701
     */
702
    private function getCommentedParamDeprecationMsg() : array
703
    {
704
        $message = 'The doctrine-bundle type commenting features were removed; the corresponding config parameter was deprecated in 2.0 and will be dropped in 3.0.';
705
706
        if (method_exists(BaseNode::class, 'getDeprecation')) {
707
            return [
708
                'doctrine/doctrine-bundle',
709
                '2.0',
710
                $message,
711
            ];
712
        }
713
714
        return [$message];
715
    }
716
}
717