Completed
Pull Request — master (#1196)
by Gocha
01:36
created

DoctrineExtension::getPhpArrayFile()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
3
namespace Doctrine\Bundle\DoctrineBundle\DependencyInjection;
4
5
use Doctrine\Bundle\DoctrineBundle\CacheWarmer\DoctrineMetadataCacheWarmer;
6
use Doctrine\Bundle\DoctrineBundle\Dbal\RegexSchemaAssetFilter;
7
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\ServiceRepositoryCompilerPass;
8
use Doctrine\Bundle\DoctrineBundle\EventSubscriber\EventSubscriberInterface;
9
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepositoryInterface;
10
use Doctrine\ORM\Proxy\Autoloader;
11
use Doctrine\ORM\UnitOfWork;
12
use LogicException;
13
use Symfony\Bridge\Doctrine\DependencyInjection\AbstractDoctrineExtension;
14
use Symfony\Bridge\Doctrine\Messenger\DoctrineClearEntityManagerWorkerSubscriber;
15
use Symfony\Bridge\Doctrine\Messenger\DoctrineTransactionMiddleware;
16
use Symfony\Bridge\Doctrine\PropertyInfo\DoctrineExtractor;
17
use Symfony\Bridge\Doctrine\SchemaListener\MessengerTransportDoctrineSchemaSubscriber;
18
use Symfony\Bridge\Doctrine\SchemaListener\PdoCacheAdapterDoctrineSchemaSubscriber;
19
use Symfony\Bridge\Doctrine\Validator\DoctrineLoader;
20
use Symfony\Component\Cache\Adapter\ArrayAdapter;
21
use Symfony\Component\Cache\Adapter\DoctrineAdapter;
22
use Symfony\Component\Cache\Adapter\PhpArrayAdapter;
23
use Symfony\Component\Cache\DoctrineProvider;
24
use Symfony\Component\Config\FileLocator;
25
use Symfony\Component\DependencyInjection\Alias;
26
use Symfony\Component\DependencyInjection\ChildDefinition;
27
use Symfony\Component\DependencyInjection\ContainerBuilder;
28
use Symfony\Component\DependencyInjection\Definition;
29
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
30
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
31
use Symfony\Component\DependencyInjection\Reference;
32
use Symfony\Component\Form\AbstractType;
33
use Symfony\Component\Messenger\Bridge\Doctrine\Transport\DoctrineTransportFactory;
34
use Symfony\Component\Messenger\MessageBusInterface;
35
use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface;
36
use function class_exists;
37
use function sprintf;
38
39
/**
40
 * DoctrineExtension is an extension for the Doctrine DBAL and ORM library.
41
 */
42
class DoctrineExtension extends AbstractDoctrineExtension
43
{
44
    /** @var string */
45
    private $defaultConnection;
46
47
    /**
48
     * {@inheritDoc}
49
     */
50
    public function load(array $configs, ContainerBuilder $container)
51
    {
52
        $configuration = $this->getConfiguration($configs, $container);
53
        $config        = $this->processConfiguration($configuration, $configs);
0 ignored issues
show
Bug introduced by
It seems like $configuration defined by $this->getConfiguration($configs, $container) on line 52 can be null; however, Symfony\Component\Depend...:processConfiguration() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
54
55
        if (! empty($config['dbal'])) {
56
            $this->dbalLoad($config['dbal'], $container);
57
58
            $this->loadMessengerServices($container);
59
        }
60
61
        if (empty($config['orm'])) {
62
            return;
63
        }
64
65
        if (empty($config['dbal'])) {
66
            throw new LogicException('Configuring the ORM layer requires to configure the DBAL layer as well.');
67
        }
68
69
        $this->ormLoad($config['orm'], $container);
70
    }
71
72
    /**
73
     * Loads the DBAL configuration.
74
     *
75
     * Usage example:
76
     *
77
     *      <doctrine:dbal id="myconn" dbname="sfweb" user="root" />
78
     *
79
     * @param array            $config    An array of configuration settings
80
     * @param ContainerBuilder $container A ContainerBuilder instance
81
     */
82
    protected function dbalLoad(array $config, ContainerBuilder $container)
83
    {
84
        $loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
85
        $loader->load('dbal.xml');
86
87
        if (empty($config['default_connection'])) {
88
            $keys                         = array_keys($config['connections']);
89
            $config['default_connection'] = reset($keys);
90
        }
91
92
        $this->defaultConnection = $config['default_connection'];
93
94
        $container->setAlias('database_connection', sprintf('doctrine.dbal.%s_connection', $this->defaultConnection));
95
        $container->getAlias('database_connection')->setPublic(true);
96
        $container->setAlias('doctrine.dbal.event_manager', new Alias(sprintf('doctrine.dbal.%s_connection.event_manager', $this->defaultConnection), false));
97
98
        $container->setParameter('doctrine.dbal.connection_factory.types', $config['types']);
99
100
        $connections = [];
101
102
        foreach (array_keys($config['connections']) as $name) {
103
            $connections[$name] = sprintf('doctrine.dbal.%s_connection', $name);
104
        }
105
106
        $container->setParameter('doctrine.connections', $connections);
107
        $container->setParameter('doctrine.default_connection', $this->defaultConnection);
108
109
        foreach ($config['connections'] as $name => $connection) {
110
            $this->loadDbalConnection($name, $connection, $container);
111
        }
112
    }
113
114
    /**
115
     * Loads a configured DBAL connection.
116
     *
117
     * @param string           $name       The name of the connection
118
     * @param array            $connection A dbal connection configuration.
119
     * @param ContainerBuilder $container  A ContainerBuilder instance
120
     */
121
    protected function loadDbalConnection($name, array $connection, ContainerBuilder $container)
122
    {
123
        $configuration = $container->setDefinition(sprintf('doctrine.dbal.%s_connection.configuration', $name), new ChildDefinition('doctrine.dbal.connection.configuration'));
124
        $logger        = null;
125
        if ($connection['logging']) {
126
            $logger = new Reference('doctrine.dbal.logger');
127
        }
128
        unset($connection['logging']);
129
130
        if ($connection['profiling']) {
131
            $profilingAbstractId = $connection['profiling_collect_backtrace'] ?
132
                'doctrine.dbal.logger.backtrace' :
133
                'doctrine.dbal.logger.profiling';
134
135
            $profilingLoggerId = $profilingAbstractId . '.' . $name;
136
            $container->setDefinition($profilingLoggerId, new ChildDefinition($profilingAbstractId));
137
            $profilingLogger = new Reference($profilingLoggerId);
138
            $container->getDefinition('data_collector.doctrine')
139
                ->addMethodCall('addLogger', [$name, $profilingLogger])
140
                ->replaceArgument(1, $connection['profiling_collect_schema_errors']);
141
142
            if ($logger !== null) {
143
                $chainLogger = new ChildDefinition('doctrine.dbal.logger.chain');
144
                $chainLogger->addMethodCall('addLogger', [$profilingLogger]);
145
146
                $loggerId = 'doctrine.dbal.logger.chain.' . $name;
147
                $container->setDefinition($loggerId, $chainLogger);
148
                $logger = new Reference($loggerId);
149
            } else {
150
                $logger = $profilingLogger;
151
            }
152
        }
153
        unset(
154
            $connection['profiling'],
155
            $connection['profiling_collect_backtrace'],
156
            $connection['profiling_collect_schema_errors']
157
        );
158
159
        if (isset($connection['auto_commit'])) {
160
            $configuration->addMethodCall('setAutoCommit', [$connection['auto_commit']]);
161
        }
162
163
        unset($connection['auto_commit']);
164
165
        if (isset($connection['schema_filter']) && $connection['schema_filter']) {
166
            $definition = new Definition(RegexSchemaAssetFilter::class, [$connection['schema_filter']]);
167
            $definition->addTag('doctrine.dbal.schema_filter', ['connection' => $name]);
168
            $container->setDefinition(sprintf('doctrine.dbal.%s_regex_schema_filter', $name), $definition);
169
        }
170
171
        unset($connection['schema_filter']);
172
173
        if ($logger) {
174
            $configuration->addMethodCall('setSQLLogger', [$logger]);
175
        }
176
177
        // event manager
178
        $container->setDefinition(sprintf('doctrine.dbal.%s_connection.event_manager', $name), new ChildDefinition('doctrine.dbal.connection.event_manager'));
179
180
        // connection
181
        $options = $this->getConnectionOptions($connection);
182
183
        $def = $container
184
            ->setDefinition(sprintf('doctrine.dbal.%s_connection', $name), new ChildDefinition('doctrine.dbal.connection'))
185
            ->setPublic(true)
186
            ->setArguments([
187
                $options,
188
                new Reference(sprintf('doctrine.dbal.%s_connection.configuration', $name)),
189
                new Reference(sprintf('doctrine.dbal.%s_connection.event_manager', $name)),
190
                $connection['mapping_types'],
191
            ]);
192
193
        // Set class in case "wrapper_class" option was used to assist IDEs
194
        if (isset($options['wrapperClass'])) {
195
            $def->setClass($options['wrapperClass']);
196
        }
197
198
        if (! empty($connection['use_savepoints'])) {
199
            $def->addMethodCall('setNestTransactionsWithSavepoints', [$connection['use_savepoints']]);
200
        }
201
202
        // Create a shard_manager for this connection
203
        if (! isset($options['shards'])) {
204
            return;
205
        }
206
207
        $shardManagerDefinition = new Definition($options['shardManagerClass'], [new Reference(sprintf('doctrine.dbal.%s_connection', $name))]);
208
        $container->setDefinition(sprintf('doctrine.dbal.%s_shard_manager', $name), $shardManagerDefinition);
209
    }
210
211
    protected function getConnectionOptions($connection)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
212
    {
213
        $options = $connection;
214
215
        if (isset($options['platform_service'])) {
216
            $options['platform'] = new Reference($options['platform_service']);
217
            unset($options['platform_service']);
218
        }
219
        unset($options['mapping_types']);
220
221
        if (isset($options['shard_choser_service'])) {
222
            $options['shard_choser'] = new Reference($options['shard_choser_service']);
223
            unset($options['shard_choser_service']);
224
        }
225
226
        foreach ([
227
            'options' => 'driverOptions',
228
            'driver_class' => 'driverClass',
229
            'wrapper_class' => 'wrapperClass',
230
            'keep_slave' => 'keepSlave',
231
            'shard_choser' => 'shardChoser',
232
            'shard_manager_class' => 'shardManagerClass',
233
            'server_version' => 'serverVersion',
234
            'default_table_options' => 'defaultTableOptions',
235
        ] as $old => $new) {
236
            if (! isset($options[$old])) {
237
                continue;
238
            }
239
240
            $options[$new] = $options[$old];
241
            unset($options[$old]);
242
        }
243
244
        if (! empty($options['slaves']) && ! empty($options['shards'])) {
245
            throw new InvalidArgumentException('Sharding and master-slave connection cannot be used together');
246
        }
247
248
        if (! empty($options['slaves'])) {
249
            $nonRewrittenKeys = [
250
                'driver' => true,
251
                'driverOptions' => true,
252
                'driverClass' => true,
253
                'wrapperClass' => true,
254
                'keepSlave' => true,
255
                'shardChoser' => true,
256
                'platform' => true,
257
                'slaves' => true,
258
                'master' => true,
259
                'shards' => true,
260
                'serverVersion' => true,
261
                'defaultTableOptions' => true,
262
                // included by safety but should have been unset already
263
                'logging' => true,
264
                'profiling' => true,
265
                'mapping_types' => true,
266
                'platform_service' => true,
267
            ];
268 View Code Duplication
            foreach ($options 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...
269
                if (isset($nonRewrittenKeys[$key])) {
270
                    continue;
271
                }
272
                $options['master'][$key] = $value;
273
                unset($options[$key]);
274
            }
275
            if (empty($options['wrapperClass'])) {
276
                // Change the wrapper class only if the user does not already forced using a custom one.
277
                $options['wrapperClass'] = 'Doctrine\\DBAL\\Connections\\MasterSlaveConnection';
278
            }
279
        } else {
280
            unset($options['slaves']);
281
        }
282
283
        if (! empty($options['shards'])) {
284
            $nonRewrittenKeys = [
285
                'driver' => true,
286
                'driverOptions' => true,
287
                'driverClass' => true,
288
                'wrapperClass' => true,
289
                'keepSlave' => true,
290
                'shardChoser' => true,
291
                'platform' => true,
292
                'slaves' => true,
293
                'global' => true,
294
                'shards' => true,
295
                'serverVersion' => true,
296
                'defaultTableOptions' => true,
297
                // included by safety but should have been unset already
298
                'logging' => true,
299
                'profiling' => true,
300
                'mapping_types' => true,
301
                'platform_service' => true,
302
            ];
303 View Code Duplication
            foreach ($options 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...
304
                if (isset($nonRewrittenKeys[$key])) {
305
                    continue;
306
                }
307
                $options['global'][$key] = $value;
308
                unset($options[$key]);
309
            }
310
            if (empty($options['wrapperClass'])) {
311
                // Change the wrapper class only if the user does not already forced using a custom one.
312
                $options['wrapperClass'] = 'Doctrine\\DBAL\\Sharding\\PoolingShardConnection';
313
            }
314
            if (empty($options['shardManagerClass'])) {
315
                // Change the shard manager class only if the user does not already forced using a custom one.
316
                $options['shardManagerClass'] = 'Doctrine\\DBAL\\Sharding\\PoolingShardManager';
317
            }
318
        } else {
319
            unset($options['shards']);
320
        }
321
322
        return $options;
323
    }
324
325
    /**
326
     * Loads the Doctrine ORM configuration.
327
     *
328
     * Usage example:
329
     *
330
     *     <doctrine:orm id="mydm" connection="myconn" />
331
     *
332
     * @param array            $config    An array of configuration settings
333
     * @param ContainerBuilder $container A ContainerBuilder instance
334
     */
335
    protected function ormLoad(array $config, ContainerBuilder $container)
336
    {
337
        if (! class_exists(UnitOfWork::class)) {
338
            throw new LogicException('To configure the ORM layer, you must first install the doctrine/orm package.');
339
        }
340
341
        $loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
342
        $loader->load('orm.xml');
343
344
        if (class_exists(AbstractType::class)) {
345
            $container->getDefinition('form.type.entity')->addTag('kernel.reset', ['method' => 'reset']);
346
        }
347
348
        // available in Symfony 5.1 and higher
349
        if (! class_exists(PdoCacheAdapterDoctrineSchemaSubscriber::class)) {
350
            $container->removeDefinition('doctrine.orm.listeners.pdo_cache_adapter_doctrine_schema_subscriber');
351
        }
352
353
        $entityManagers = [];
354
        foreach (array_keys($config['entity_managers']) as $name) {
355
            $entityManagers[$name] = sprintf('doctrine.orm.%s_entity_manager', $name);
356
        }
357
        $container->setParameter('doctrine.entity_managers', $entityManagers);
358
359
        if (empty($config['default_entity_manager'])) {
360
            $tmp                              = array_keys($entityManagers);
361
            $config['default_entity_manager'] = reset($tmp);
362
        }
363
        $container->setParameter('doctrine.default_entity_manager', $config['default_entity_manager']);
364
365
        $options = ['auto_generate_proxy_classes', 'proxy_dir', 'proxy_namespace'];
366
        foreach ($options as $key) {
367
            $container->setParameter('doctrine.orm.' . $key, $config[$key]);
368
        }
369
370
        $container->setAlias('doctrine.orm.entity_manager', $defaultEntityManagerDefinitionId = sprintf('doctrine.orm.%s_entity_manager', $config['default_entity_manager']));
371
        $container->getAlias('doctrine.orm.entity_manager')->setPublic(true);
372
373
        $config['entity_managers'] = $this->fixManagersAutoMappings($config['entity_managers'], $container->getParameter('kernel.bundles'));
374
375
        foreach ($config['entity_managers'] as $name => $entityManager) {
376
            $entityManager['name'] = $name;
377
            $this->loadOrmEntityManager($entityManager, $container);
378
379
            if (interface_exists(PropertyInfoExtractorInterface::class)) {
380
                $this->loadPropertyInfoExtractor($name, $container);
381
            }
382
383
            $this->loadValidatorLoader($name, $container);
384
        }
385
386
        if ($config['resolve_target_entities']) {
387
            $def = $container->findDefinition('doctrine.orm.listeners.resolve_target_entity');
388
            foreach ($config['resolve_target_entities'] as $name => $implementation) {
389
                $def->addMethodCall('addResolveTargetEntity', [
390
                    $name,
391
                    $implementation,
392
                    [],
393
                ]);
394
            }
395
396
            $def->addTag('doctrine.event_subscriber');
397
        }
398
399
        $container->registerForAutoconfiguration(ServiceEntityRepositoryInterface::class)
400
            ->addTag(ServiceRepositoryCompilerPass::REPOSITORY_SERVICE_TAG);
401
402
        $container->registerForAutoconfiguration(EventSubscriberInterface::class)
403
            ->addTag('doctrine.event_subscriber');
404
405
        /**
406
         * @see DoctrineBundle::boot()
407
         */
408
        $container->getDefinition($defaultEntityManagerDefinitionId)
409
            ->addTag('container.preload', [
410
                'class' => Autoloader::class,
411
            ]);
412
    }
413
414
    /**
415
     * Loads a configured ORM entity manager.
416
     *
417
     * @param array            $entityManager A configured ORM entity manager.
418
     * @param ContainerBuilder $container     A ContainerBuilder instance
419
     */
420
    protected function loadOrmEntityManager(array $entityManager, ContainerBuilder $container)
421
    {
422
        $ormConfigDef = $container->setDefinition(sprintf('doctrine.orm.%s_configuration', $entityManager['name']), new ChildDefinition('doctrine.orm.configuration'));
423
424
        $this->loadOrmEntityManagerMappingInformation($entityManager, $ormConfigDef, $container);
425
        $this->loadOrmCacheDrivers($entityManager, $container);
426
427
        if (isset($entityManager['entity_listener_resolver']) && $entityManager['entity_listener_resolver']) {
428
            $container->setAlias(sprintf('doctrine.orm.%s_entity_listener_resolver', $entityManager['name']), $entityManager['entity_listener_resolver']);
429
        } else {
430
            $definition = new Definition('%doctrine.orm.entity_listener_resolver.class%');
431
            $definition->addArgument(new Reference('service_container'));
432
            $container->setDefinition(sprintf('doctrine.orm.%s_entity_listener_resolver', $entityManager['name']), $definition);
433
        }
434
435
        $methods = [
436
            'setMetadataCacheImpl' => new Reference(sprintf('doctrine.orm.%s_metadata_cache', $entityManager['name'])),
437
            'setQueryCacheImpl' => new Reference(sprintf('doctrine.orm.%s_query_cache', $entityManager['name'])),
438
            'setResultCacheImpl' => new Reference(sprintf('doctrine.orm.%s_result_cache', $entityManager['name'])),
439
            'setMetadataDriverImpl' => new Reference('doctrine.orm.' . $entityManager['name'] . '_metadata_driver'),
440
            'setProxyDir' => '%doctrine.orm.proxy_dir%',
441
            'setProxyNamespace' => '%doctrine.orm.proxy_namespace%',
442
            'setAutoGenerateProxyClasses' => '%doctrine.orm.auto_generate_proxy_classes%',
443
            'setClassMetadataFactoryName' => $entityManager['class_metadata_factory_name'],
444
            'setDefaultRepositoryClassName' => $entityManager['default_repository_class'],
445
            'setNamingStrategy' => new Reference($entityManager['naming_strategy']),
446
            'setQuoteStrategy' => new Reference($entityManager['quote_strategy']),
447
            'setEntityListenerResolver' => new Reference(sprintf('doctrine.orm.%s_entity_listener_resolver', $entityManager['name'])),
448
        ];
449
450
        $listenerId        = sprintf('doctrine.orm.%s_listeners.attach_entity_listeners', $entityManager['name']);
451
        $listenerDef       = $container->setDefinition($listenerId, new Definition('%doctrine.orm.listeners.attach_entity_listeners.class%'));
452
        $listenerTagParams = ['event' => 'loadClassMetadata'];
453
        if (isset($entityManager['connection'])) {
454
            $listenerTagParams['connection'] = $entityManager['connection'];
455
        }
456
        $listenerDef->addTag('doctrine.event_listener', $listenerTagParams);
457
458
        if (isset($entityManager['second_level_cache'])) {
459
            $this->loadOrmSecondLevelCache($entityManager, $ormConfigDef, $container);
460
        }
461
462
        if ($entityManager['repository_factory']) {
463
            $methods['setRepositoryFactory'] = new Reference($entityManager['repository_factory']);
464
        }
465
466
        foreach ($methods as $method => $arg) {
467
            $ormConfigDef->addMethodCall($method, [$arg]);
468
        }
469
470
        foreach ($entityManager['hydrators'] as $name => $class) {
471
            $ormConfigDef->addMethodCall('addCustomHydrationMode', [$name, $class]);
472
        }
473
474
        if (! empty($entityManager['dql'])) {
475
            foreach ($entityManager['dql']['string_functions'] as $name => $function) {
476
                $ormConfigDef->addMethodCall('addCustomStringFunction', [$name, $function]);
477
            }
478
            foreach ($entityManager['dql']['numeric_functions'] as $name => $function) {
479
                $ormConfigDef->addMethodCall('addCustomNumericFunction', [$name, $function]);
480
            }
481
            foreach ($entityManager['dql']['datetime_functions'] as $name => $function) {
482
                $ormConfigDef->addMethodCall('addCustomDatetimeFunction', [$name, $function]);
483
            }
484
        }
485
486
        $enabledFilters    = [];
487
        $filtersParameters = [];
488
        foreach ($entityManager['filters'] as $name => $filter) {
489
            $ormConfigDef->addMethodCall('addFilter', [$name, $filter['class']]);
490
            if ($filter['enabled']) {
491
                $enabledFilters[] = $name;
492
            }
493
            if (! $filter['parameters']) {
494
                continue;
495
            }
496
497
            $filtersParameters[$name] = $filter['parameters'];
498
        }
499
500
        $managerConfiguratorName = sprintf('doctrine.orm.%s_manager_configurator', $entityManager['name']);
501
        $container
502
            ->setDefinition($managerConfiguratorName, new ChildDefinition('doctrine.orm.manager_configurator.abstract'))
503
            ->replaceArgument(0, $enabledFilters)
504
            ->replaceArgument(1, $filtersParameters);
505
506
        if (! isset($entityManager['connection'])) {
507
            $entityManager['connection'] = $this->defaultConnection;
508
        }
509
510
        $container
511
            ->setDefinition(sprintf('doctrine.orm.%s_entity_manager', $entityManager['name']), new ChildDefinition('doctrine.orm.entity_manager.abstract'))
512
            ->setPublic(true)
513
            ->setArguments([
514
                new Reference(sprintf('doctrine.dbal.%s_connection', $entityManager['connection'])),
515
                new Reference(sprintf('doctrine.orm.%s_configuration', $entityManager['name'])),
516
            ])
517
            ->setConfigurator([new Reference($managerConfiguratorName), 'configure']);
518
519
        $container->setAlias(
520
            sprintf('doctrine.orm.%s_entity_manager.event_manager', $entityManager['name']),
521
            new Alias(sprintf('doctrine.dbal.%s_connection.event_manager', $entityManager['connection']), false)
522
        );
523
524
        if (! isset($entityManager['entity_listeners'])) {
525
            return;
526
        }
527
528
        if (! isset($listenerDef)) {
529
            throw new InvalidArgumentException('Entity listeners configuration requires doctrine-orm 2.5.0 or newer');
530
        }
531
532
        $entities = $entityManager['entity_listeners']['entities'];
533
534
        foreach ($entities as $entityListenerClass => $entity) {
535
            foreach ($entity['listeners'] as $listenerClass => $listener) {
536
                foreach ($listener['events'] as $listenerEvent) {
537
                    $listenerEventName = $listenerEvent['type'];
538
                    $listenerMethod    = $listenerEvent['method'];
539
540
                    $listenerDef->addMethodCall('addEntityListener', [
541
                        $entityListenerClass,
542
                        $listenerClass,
543
                        $listenerEventName,
544
                        $listenerMethod,
545
                    ]);
546
                }
547
            }
548
        }
549
    }
550
551
    /**
552
     * Loads an ORM entity managers bundle mapping information.
553
     *
554
     * There are two distinct configuration possibilities for mapping information:
555
     *
556
     * 1. Specify a bundle and optionally details where the entity and mapping information reside.
557
     * 2. Specify an arbitrary mapping location.
558
     *
559
     * @param array            $entityManager A configured ORM entity manager
560
     * @param Definition       $ormConfigDef  A Definition instance
561
     * @param ContainerBuilder $container     A ContainerBuilder instance
562
     *
563
     * @example
564
     *
565
     *  doctrine.orm:
566
     *     mappings:
567
     *         MyBundle1: ~
568
     *         MyBundle2: yml
569
     *         MyBundle3: { type: annotation, dir: Entities/ }
570
     *         MyBundle4: { type: xml, dir: Resources/config/doctrine/mapping }
571
     *         MyBundle5:
572
     *             type: yml
573
     *             dir: bundle-mappings/
574
     *             alias: BundleAlias
575
     *         arbitrary_key:
576
     *             type: xml
577
     *             dir: %kernel.project_dir%/src/vendor/DoctrineExtensions/lib/DoctrineExtensions/Entities
578
     *             prefix: DoctrineExtensions\Entities\
579
     *             alias: DExt
580
     *
581
     * In the case of bundles everything is really optional (which leads to autodetection for this bundle) but
582
     * in the mappings key everything except alias is a required argument.
583
     */
584
    protected function loadOrmEntityManagerMappingInformation(array $entityManager, Definition $ormConfigDef, ContainerBuilder $container)
585
    {
586
        // reset state of drivers and alias map. They are only used by this methods and children.
587
        $this->drivers  = [];
588
        $this->aliasMap = [];
589
590
        $this->loadMappingInformation($entityManager, $container);
591
        $this->registerMappingDrivers($entityManager, $container);
592
593
        $ormConfigDef->addMethodCall('setEntityNamespaces', [$this->aliasMap]);
594
    }
595
596
    /**
597
     * Loads an ORM second level cache bundle mapping information.
598
     *
599
     * @param array            $entityManager A configured ORM entity manager
600
     * @param Definition       $ormConfigDef  A Definition instance
601
     * @param ContainerBuilder $container     A ContainerBuilder instance
602
     *
603
     * @example
604
     *  entity_managers:
605
     *      default:
606
     *          second_level_cache:
607
     *              region_cache_driver: apc
608
     *              log_enabled: true
609
     *              regions:
610
     *                  my_service_region:
611
     *                      type: service
612
     *                      service : "my_service_region"
613
     *
614
     *                  my_query_region:
615
     *                      lifetime: 300
616
     *                      cache_driver: array
617
     *                      type: filelock
618
     *
619
     *                  my_entity_region:
620
     *                      lifetime: 600
621
     *                      cache_driver:
622
     *                          type: apc
623
     */
624
    protected function loadOrmSecondLevelCache(array $entityManager, Definition $ormConfigDef, ContainerBuilder $container)
625
    {
626
        $driverId = null;
627
        $enabled  = $entityManager['second_level_cache']['enabled'];
628
629
        if (isset($entityManager['second_level_cache']['region_cache_driver'])) {
630
            $driverName = 'second_level_cache.region_cache_driver';
631
            $driverMap  = $entityManager['second_level_cache']['region_cache_driver'];
632
            $driverId   = $this->loadCacheDriver($driverName, $entityManager['name'], $driverMap, $container);
633
        }
634
635
        $configId   = sprintf('doctrine.orm.%s_second_level_cache.cache_configuration', $entityManager['name']);
636
        $regionsId  = sprintf('doctrine.orm.%s_second_level_cache.regions_configuration', $entityManager['name']);
637
        $driverId   = $driverId ?: sprintf('doctrine.orm.%s_second_level_cache.region_cache_driver', $entityManager['name']);
638
        $configDef  = $container->setDefinition($configId, new Definition('%doctrine.orm.second_level_cache.cache_configuration.class%'));
639
        $regionsDef = $container->setDefinition($regionsId, new Definition('%doctrine.orm.second_level_cache.regions_configuration.class%'));
640
641
        $slcFactoryId = sprintf('doctrine.orm.%s_second_level_cache.default_cache_factory', $entityManager['name']);
642
        $factoryClass = isset($entityManager['second_level_cache']['factory']) ? $entityManager['second_level_cache']['factory'] : '%doctrine.orm.second_level_cache.default_cache_factory.class%';
643
644
        $definition = new Definition($factoryClass, [new Reference($regionsId), new Reference($driverId)]);
645
646
        $slcFactoryDef = $container
647
            ->setDefinition($slcFactoryId, $definition);
648
649
        if (isset($entityManager['second_level_cache']['regions'])) {
650
            foreach ($entityManager['second_level_cache']['regions'] as $name => $region) {
651
                $regionRef  = null;
652
                $regionType = $region['type'];
653
654
                if ($regionType === 'service') {
655
                    $regionId  = sprintf('doctrine.orm.%s_second_level_cache.region.%s', $entityManager['name'], $name);
656
                    $regionRef = new Reference($region['service']);
657
658
                    $container->setAlias($regionId, new Alias($region['service'], false));
659
                }
660
661
                if ($regionType === 'default' || $regionType === 'filelock') {
662
                    $regionId   = sprintf('doctrine.orm.%s_second_level_cache.region.%s', $entityManager['name'], $name);
663
                    $driverName = sprintf('second_level_cache.region.%s_driver', $name);
664
                    $driverMap  = $region['cache_driver'];
665
                    $driverId   = $this->loadCacheDriver($driverName, $entityManager['name'], $driverMap, $container);
666
                    $regionRef  = new Reference($regionId);
667
668
                    $container
669
                        ->setDefinition($regionId, new Definition('%doctrine.orm.second_level_cache.default_region.class%'))
670
                        ->setArguments([$name, new Reference($driverId), $region['lifetime']]);
671
                }
672
673
                if ($regionType === 'filelock') {
674
                    $regionId = sprintf('doctrine.orm.%s_second_level_cache.region.%s_filelock', $entityManager['name'], $name);
675
676
                    $container
677
                        ->setDefinition($regionId, new Definition('%doctrine.orm.second_level_cache.filelock_region.class%'))
678
                        ->setArguments([$regionRef, $region['lock_path'], $region['lock_lifetime']]);
679
680
                    $regionRef = new Reference($regionId);
681
                    $regionsDef->addMethodCall('getLockLifetime', [$name, $region['lock_lifetime']]);
682
                }
683
684
                $regionsDef->addMethodCall('setLifetime', [$name, $region['lifetime']]);
685
                $slcFactoryDef->addMethodCall('setRegion', [$regionRef]);
686
            }
687
        }
688
689
        if ($entityManager['second_level_cache']['log_enabled']) {
690
            $loggerChainId   = sprintf('doctrine.orm.%s_second_level_cache.logger_chain', $entityManager['name']);
691
            $loggerStatsId   = sprintf('doctrine.orm.%s_second_level_cache.logger_statistics', $entityManager['name']);
692
            $loggerChaingDef = $container->setDefinition($loggerChainId, new Definition('%doctrine.orm.second_level_cache.logger_chain.class%'));
693
            $loggerStatsDef  = $container->setDefinition($loggerStatsId, new Definition('%doctrine.orm.second_level_cache.logger_statistics.class%'));
694
695
            $loggerChaingDef->addMethodCall('setLogger', ['statistics', $loggerStatsDef]);
696
            $configDef->addMethodCall('setCacheLogger', [$loggerChaingDef]);
697
698
            foreach ($entityManager['second_level_cache']['loggers'] as $name => $logger) {
699
                $loggerId  = sprintf('doctrine.orm.%s_second_level_cache.logger.%s', $entityManager['name'], $name);
700
                $loggerRef = new Reference($logger['service']);
701
702
                $container->setAlias($loggerId, new Alias($logger['service'], false));
703
                $loggerChaingDef->addMethodCall('setLogger', [$name, $loggerRef]);
704
            }
705
        }
706
707
        $configDef->addMethodCall('setCacheFactory', [$slcFactoryDef]);
708
        $configDef->addMethodCall('setRegionsConfiguration', [$regionsDef]);
709
        $ormConfigDef->addMethodCall('setSecondLevelCacheEnabled', [$enabled]);
710
        $ormConfigDef->addMethodCall('setSecondLevelCacheConfiguration', [$configDef]);
711
    }
712
713
    /**
714
     * {@inheritDoc}
715
     */
716
    protected function getObjectManagerElementName($name) : string
717
    {
718
        return 'doctrine.orm.' . $name;
719
    }
720
721
    protected function getMappingObjectDefaultName() : string
722
    {
723
        return 'Entity';
724
    }
725
726
    /**
727
     * {@inheritDoc}
728
     */
729
    protected function getMappingResourceConfigDirectory() : string
730
    {
731
        return 'Resources/config/doctrine';
732
    }
733
734
    /**
735
     * {@inheritDoc}
736
     */
737
    protected function getMappingResourceExtension() : string
738
    {
739
        return 'orm';
740
    }
741
742
    /**
743
     * {@inheritDoc}
744
     */
745
    protected function loadCacheDriver($cacheName, $objectManagerName, array $cacheDriver, ContainerBuilder $container) : string
746
    {
747
        $serviceId = null;
0 ignored issues
show
Unused Code introduced by
$serviceId is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
748
        $aliasId   = $this->getObjectManagerElementName(sprintf('%s_%s', $objectManagerName, $cacheName));
749
750
        switch ($cacheDriver['type'] ?? 'pool') {
751
            case 'service':
752
                $serviceId = $cacheDriver['id'];
753
                break;
754
755
            case 'pool':
756
                $serviceId = $this->createPoolCacheDefinition($container, $cacheDriver['pool'] ?? $this->createArrayAdapterCachePool($container, $objectManagerName, $cacheName));
757
                break;
758
759
            default:
760
                throw new \InvalidArgumentException(sprintf(
761
                    'Unknown cache of type "%s" configured for cache "%s" in entity manager "%s".',
762
                    $cacheDriver['type'],
763
                    $cacheName,
764
                    $objectManagerName
765
                ));
766
        }
767
768
        $container->setAlias($aliasId, new Alias($serviceId, false));
769
770
        return $aliasId;
771
    }
772
773
    /**
774
     * Loads a configured entity managers cache drivers.
775
     *
776
     * @param array            $entityManager A configured ORM entity manager.
777
     * @param ContainerBuilder $container     A ContainerBuilder instance
778
     */
779
    protected function loadOrmCacheDrivers(array $entityManager, ContainerBuilder $container)
780
    {
781
        $this->loadCacheDriver('metadata_cache', $entityManager['name'], $entityManager['metadata_cache_driver'], $container);
782
        $this->loadCacheDriver('result_cache', $entityManager['name'], $entityManager['result_cache_driver'], $container);
783
        $this->loadCacheDriver('query_cache', $entityManager['name'], $entityManager['query_cache_driver'], $container);
784
785
        if (!$container->getParameter('kernel.debug')) {
786
            $this->registerMetadataPhpArrayCacheWarmer($entityManager['name'], $container);
787
            $this->registerMetadataPhpArrayCache($entityManager['name'], $container);
788
        }
789
    }
790
791
    /**
792
     * Loads a property info extractor for each defined entity manager.
793
     */
794
    private function loadPropertyInfoExtractor(string $entityManagerName, ContainerBuilder $container) : void
795
    {
796
        $propertyExtractorDefinition = $container->register(sprintf('doctrine.orm.%s_entity_manager.property_info_extractor', $entityManagerName), DoctrineExtractor::class);
797
        $argumentId                  = sprintf('doctrine.orm.%s_entity_manager', $entityManagerName);
798
799
        $propertyExtractorDefinition->addArgument(new Reference($argumentId));
800
801
        $propertyExtractorDefinition->addTag('property_info.list_extractor', ['priority' => -1001]);
802
        $propertyExtractorDefinition->addTag('property_info.type_extractor', ['priority' => -999]);
803
        $propertyExtractorDefinition->addTag('property_info.access_extractor', ['priority' => -999]);
804
    }
805
806
    /**
807
     * Loads a validator loader for each defined entity manager.
808
     */
809 View Code Duplication
    private function loadValidatorLoader(string $entityManagerName, ContainerBuilder $container) : void
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
810
    {
811
        $validatorLoaderDefinition = $container->register(sprintf('doctrine.orm.%s_entity_manager.validator_loader', $entityManagerName), DoctrineLoader::class);
812
        $validatorLoaderDefinition->addArgument(new Reference(sprintf('doctrine.orm.%s_entity_manager', $entityManagerName)));
813
814
        $validatorLoaderDefinition->addTag('validator.auto_mapper', ['priority' => -100]);
815
    }
816
817
    /**
818
     * @param array  $objectManager
819
     * @param string $cacheName
820
     */
821
    public function loadObjectManagerCacheDriver(array $objectManager, ContainerBuilder $container, $cacheName)
822
    {
823
        $this->loadCacheDriver($cacheName, $objectManager['name'], $objectManager[$cacheName . '_driver'], $container);
824
    }
825
826
    /**
827
     * {@inheritDoc}
828
     */
829
    public function getXsdValidationBasePath() : string
830
    {
831
        return __DIR__ . '/../Resources/config/schema';
832
    }
833
834
    /**
835
     * {@inheritDoc}
836
     */
837
    public function getNamespace() : string
838
    {
839
        return 'http://symfony.com/schema/dic/doctrine';
840
    }
841
842
    /**
843
     * {@inheritDoc}
844
     */
845
    public function getConfiguration(array $config, ContainerBuilder $container) : Configuration
846
    {
847
        return new Configuration($container->getParameter('kernel.debug'));
848
    }
849
850
    protected function getMetadataDriverClass(string $driverType) : string
851
    {
852
        return '%' . $this->getObjectManagerElementName('metadata.' . $driverType . '.class%');
853
    }
854
855
    private function loadMessengerServices(ContainerBuilder $container) : void
856
    {
857
        // If the Messenger component is installed and the doctrine transaction middleware is available, wire it:
858
        if (! interface_exists(MessageBusInterface::class) || ! class_exists(DoctrineTransactionMiddleware::class)) {
859
            return;
860
        }
861
862
        $loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
863
        $loader->load('messenger.xml');
864
865
        if (! class_exists(DoctrineClearEntityManagerWorkerSubscriber::class)) {
866
            $container->removeDefinition('doctrine.orm.messenger.event_subscriber.doctrine_clear_entity_manager');
867
        }
868
869
        // available in Symfony 5.1 and higher
870
        if (! class_exists(MessengerTransportDoctrineSchemaSubscriber::class)) {
871
            $container->removeDefinition('doctrine.orm.messenger.doctrine_schema_subscriber');
872
        }
873
874
        $transportFactoryDefinition = $container->getDefinition('messenger.transport.doctrine.factory');
875
        if (! class_exists(DoctrineTransportFactory::class)) {
876
            // If symfony/messenger < 5.1
877
            if (! class_exists(\Symfony\Component\Messenger\Transport\Doctrine\DoctrineTransportFactory::class)) {
878
                // Dont add the tag
879
                return;
880
            }
881
882
            $transportFactoryDefinition->setClass(\Symfony\Component\Messenger\Transport\Doctrine\DoctrineTransportFactory::class);
883
        }
884
885
        $transportFactoryDefinition->addTag('messenger.transport_factory');
886
    }
887
888
    private function createPoolCacheDefinition(ContainerBuilder $container, string $poolName) : string
889
    {
890
        $serviceId = sprintf('doctrine.orm.cache.provider.%s', $poolName);
891
892
        $definition = $container->register($serviceId, DoctrineProvider::class);
893
        $definition->addArgument(new Reference($poolName));
894
        $definition->setPrivate(true);
0 ignored issues
show
Deprecated Code introduced by
The method Symfony\Component\Depend...efinition::setPrivate() has been deprecated with message: since Symfony 5.2, use setPublic() instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
895
896
        return $serviceId;
897
    }
898
899
    private function createArrayAdapterCachePool(ContainerBuilder $container, string $objectManagerName, string $cacheName) : string
900
    {
901
        $id = sprintf('cache.doctrine.orm.%s.%s', $objectManagerName, str_replace('_cache', '', $cacheName));
902
903
        $poolDefinition = $container->register($id, ArrayAdapter::class);
904
        $poolDefinition->addTag('cache.pool');
905
        $container->setDefinition($id, $poolDefinition);
906
907
        return $id;
908
    }
909
910 View Code Duplication
    private function registerMetadataPhpArrayCacheWarmer(string $entityManagerName, ContainerBuilder $container): void
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
911
    {
912
        $cacheWarmerDefinition = $container->register(sprintf('doctrine.orm.%s_metadata_cache.php_array_warmer', $entityManagerName), DoctrineMetadataCacheWarmer::class);
913
        $cacheWarmerDefinition->setArguments([
914
            new Reference(sprintf('doctrine.orm.%s_entity_manager', $entityManagerName)),
915
            $this->getPhpArrayFile($entityManagerName)
916
        ]);
917
        $cacheWarmerDefinition->addTag('kernel.cache_warmer');
918
    }
919
920
    private function registerMetadataPhpArrayCache(string $entityManagerName, ContainerBuilder $container): void
921
    {
922
        $aliasId = sprintf('doctrine.orm.%s_metadata_cache', $entityManagerName);
923
        $serviceId = (string) $container->getAlias($aliasId);
924
        $phpArrayAdapterDefinition = new Definition(PhpArrayAdapter::class, [
925
            $this->getPhpArrayFile($entityManagerName),
926
            new Definition(DoctrineAdapter::class, [new Reference($serviceId)]),
927
        ]);
928
        $serviceId = $serviceId.'.php_array';
929
        $doctrineCacheDefinition = $container->register($serviceId, DoctrineProvider::class);
930
        $doctrineCacheDefinition->addArgument($phpArrayAdapterDefinition);
931
        $container->setAlias($aliasId, $serviceId);
932
    }
933
934
    private function getPhpArrayFile(string $entityManagerName): string
935
    {
936
        return '%kernel.cache_dir%'.sprintf('/doctrine/orm/%s_metadata.php', $entityManagerName);
937
    }
938
}
939