Completed
Pull Request — master (#1010)
by Piotr
02:11
created

DoctrineExtension::load()   B

Complexity

Conditions 6
Paths 12

Size

Total Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 25
rs 8.8977
c 0
b 0
f 0
cc 6
nc 12
nop 2
1
<?php
2
3
namespace Doctrine\Bundle\DoctrineBundle\DependencyInjection;
4
5
use Doctrine\Bundle\DoctrineBundle\Dbal\RegexSchemaAssetFilter;
6
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\ServiceRepositoryCompilerPass;
7
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepositoryInterface;
8
use Doctrine\Bundle\DoctrineCacheBundle\DependencyInjection\CacheProviderLoader;
9
use Doctrine\Bundle\DoctrineCacheBundle\DependencyInjection\SymfonyBridgeAdapter;
10
use Doctrine\Common\Persistence\Mapping\ClassMetadataFactory;
11
use Doctrine\ORM\Version;
12
use LogicException;
13
use Symfony\Bridge\Doctrine\DependencyInjection\AbstractDoctrineExtension;
14
use Symfony\Bridge\Doctrine\Messenger\DoctrineTransactionMiddleware;
15
use Symfony\Bridge\Doctrine\PropertyInfo\DoctrineExtractor;
16
use Symfony\Bridge\Doctrine\Validator\DoctrineLoader;
17
use Symfony\Component\Cache\DoctrineProvider;
18
use Symfony\Component\Config\FileLocator;
19
use Symfony\Component\DependencyInjection\Alias;
20
use Symfony\Component\DependencyInjection\ChildDefinition;
21
use Symfony\Component\DependencyInjection\ContainerBuilder;
22
use Symfony\Component\DependencyInjection\Definition;
23
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
24
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
25
use Symfony\Component\DependencyInjection\Reference;
26
use Symfony\Component\Form\AbstractType;
27
use Symfony\Component\Messenger\MessageBusInterface;
28
use Symfony\Component\Messenger\Transport\Doctrine\DoctrineTransportFactory;
29
use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface;
30
use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface;
31
use Symfony\Component\Validator\Mapping\Loader\LoaderInterface;
32
use function class_exists;
33
use function sprintf;
34
35
/**
36
 * DoctrineExtension is an extension for the Doctrine DBAL and ORM library.
37
 */
38
class DoctrineExtension extends AbstractDoctrineExtension
39
{
40
    /** @var string */
41
    private $defaultConnection;
42
43
    /** @var SymfonyBridgeAdapter */
44
    private $adapter;
45
46
    public function __construct(SymfonyBridgeAdapter $adapter = null)
47
    {
48
        $this->adapter = $adapter ?: new SymfonyBridgeAdapter(new CacheProviderLoader(), 'doctrine.orm', 'orm');
49
    }
50
51
    /**
52
     * {@inheritDoc}
53
     */
54
    public function load(array $configs, ContainerBuilder $container)
55
    {
56
        $configuration = $this->getConfiguration($configs, $container);
57
        $config        = $this->processConfiguration($configuration, $configs);
0 ignored issues
show
Bug introduced by
It seems like $configuration defined by $this->getConfiguration($configs, $container) on line 56 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...
58
59
        $this->adapter->loadServicesConfiguration($container);
60
61
        if (! empty($config['dbal'])) {
62
            $this->dbalLoad($config['dbal'], $container);
63
        }
64
65
        if (! empty($config['orm'])) {
66
            $this->ormLoad($config['orm'], $container);
67
        }
68
69
        if (empty($config['dbal'])) {
70
            throw new LogicException('Configuring the ORM layer requires to configure the DBAL layer as well.');
71
        }
72
73
        if (! empty($config['orm']) && ! class_exists(Version::class)) {
74
            throw new LogicException('To configure the ORM layer, you must first install the doctrine/orm package.');
75
        }
76
77
        $this->loadMessengerServices($container);
78
    }
79
80
    /**
81
     * Loads the DBAL configuration.
82
     *
83
     * Usage example:
84
     *
85
     *      <doctrine:dbal id="myconn" dbname="sfweb" user="root" />
86
     *
87
     * @param array            $config    An array of configuration settings
88
     * @param ContainerBuilder $container A ContainerBuilder instance
89
     */
90
    protected function dbalLoad(array $config, ContainerBuilder $container)
91
    {
92
        $loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
93
        $loader->load('dbal.xml');
94
95
        if (empty($config['default_connection'])) {
96
            $keys                         = array_keys($config['connections']);
97
            $config['default_connection'] = reset($keys);
98
        }
99
100
        $this->defaultConnection = $config['default_connection'];
101
102
        $container->setAlias('database_connection', sprintf('doctrine.dbal.%s_connection', $this->defaultConnection));
103
        $container->getAlias('database_connection')->setPublic(true);
104
        $container->setAlias('doctrine.dbal.event_manager', new Alias(sprintf('doctrine.dbal.%s_connection.event_manager', $this->defaultConnection), false));
105
106
        $container->setParameter('doctrine.dbal.connection_factory.types', $config['types']);
107
108
        $connections = [];
109
110
        foreach (array_keys($config['connections']) as $name) {
111
            $connections[$name] = sprintf('doctrine.dbal.%s_connection', $name);
112
        }
113
114
        $container->setParameter('doctrine.connections', $connections);
115
        $container->setParameter('doctrine.default_connection', $this->defaultConnection);
116
117
        foreach ($config['connections'] as $name => $connection) {
118
            $this->loadDbalConnection($name, $connection, $container);
119
        }
120
    }
121
122
    /**
123
     * Loads a configured DBAL connection.
124
     *
125
     * @param string           $name       The name of the connection
126
     * @param array            $connection A dbal connection configuration.
127
     * @param ContainerBuilder $container  A ContainerBuilder instance
128
     */
129
    protected function loadDbalConnection($name, array $connection, ContainerBuilder $container)
130
    {
131
        $configuration = $container->setDefinition(sprintf('doctrine.dbal.%s_connection.configuration', $name), new ChildDefinition('doctrine.dbal.connection.configuration'));
132
        $logger        = null;
133
        if ($connection['logging']) {
134
            $logger = new Reference('doctrine.dbal.logger');
135
        }
136
        unset($connection['logging']);
137
138
        if ($connection['profiling']) {
139
            $profilingAbstractId = $connection['profiling_collect_backtrace'] ?
140
                'doctrine.dbal.logger.backtrace' :
141
                'doctrine.dbal.logger.profiling';
142
143
            $profilingLoggerId = $profilingAbstractId . '.' . $name;
144
            $container->setDefinition($profilingLoggerId, new ChildDefinition($profilingAbstractId));
145
            $profilingLogger = new Reference($profilingLoggerId);
146
            $container->getDefinition('data_collector.doctrine')->addMethodCall('addLogger', [$name, $profilingLogger]);
147
148
            if ($logger !== null) {
149
                $chainLogger = new ChildDefinition('doctrine.dbal.logger.chain');
150
                $chainLogger->addMethodCall('addLogger', [$profilingLogger]);
151
152
                $loggerId = 'doctrine.dbal.logger.chain.' . $name;
153
                $container->setDefinition($loggerId, $chainLogger);
154
                $logger = new Reference($loggerId);
155
            } else {
156
                $logger = $profilingLogger;
157
            }
158
        }
159
        unset($connection['profiling'], $connection['profiling_collect_backtrace']);
160
161
        if (isset($connection['auto_commit'])) {
162
            $configuration->addMethodCall('setAutoCommit', [$connection['auto_commit']]);
163
        }
164
165
        unset($connection['auto_commit']);
166
167
        if (isset($connection['schema_filter']) && $connection['schema_filter']) {
168
            if (method_exists(\Doctrine\DBAL\Configuration::class, 'setSchemaAssetsFilter')) {
169
                $definition = new Definition(RegexSchemaAssetFilter::class, [$connection['schema_filter']]);
170
                $definition->addTag('doctrine.dbal.schema_filter', ['connection' => $name]);
171
                $container->setDefinition(sprintf('doctrine.dbal.%s_regex_schema_filter', $name), $definition);
172
            } else {
173
                // backwards compatibility with dbal < 2.9
174
                $configuration->addMethodCall('setFilterSchemaAssetsExpression', [$connection['schema_filter']]);
175
            }
176
        }
177
178
        unset($connection['schema_filter']);
179
180
        if ($logger) {
181
            $configuration->addMethodCall('setSQLLogger', [$logger]);
182
        }
183
184
        // event manager
185
        $container->setDefinition(sprintf('doctrine.dbal.%s_connection.event_manager', $name), new ChildDefinition('doctrine.dbal.connection.event_manager'));
186
187
        // connection
188
        $options = $this->getConnectionOptions($connection);
189
190
        $def = $container
191
            ->setDefinition(sprintf('doctrine.dbal.%s_connection', $name), new ChildDefinition('doctrine.dbal.connection'))
192
            ->setPublic(true)
193
            ->setArguments([
194
                $options,
195
                new Reference(sprintf('doctrine.dbal.%s_connection.configuration', $name)),
196
                new Reference(sprintf('doctrine.dbal.%s_connection.event_manager', $name)),
197
                $connection['mapping_types'],
198
            ]);
199
200
        // Set class in case "wrapper_class" option was used to assist IDEs
201
        if (isset($options['wrapperClass'])) {
202
            $def->setClass($options['wrapperClass']);
203
        }
204
205
        if (! empty($connection['use_savepoints'])) {
206
            $def->addMethodCall('setNestTransactionsWithSavepoints', [$connection['use_savepoints']]);
207
        }
208
209
        // Create a shard_manager for this connection
210
        if (! isset($options['shards'])) {
211
            return;
212
        }
213
214
        $shardManagerDefinition = new Definition($options['shardManagerClass'], [new Reference(sprintf('doctrine.dbal.%s_connection', $name))]);
215
        $container->setDefinition(sprintf('doctrine.dbal.%s_shard_manager', $name), $shardManagerDefinition);
216
    }
217
218
    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...
219
    {
220
        $options = $connection;
221
222
        if (isset($options['platform_service'])) {
223
            $options['platform'] = new Reference($options['platform_service']);
224
            unset($options['platform_service']);
225
        }
226
        unset($options['mapping_types']);
227
228
        if (isset($options['shard_choser_service'])) {
229
            $options['shard_choser'] = new Reference($options['shard_choser_service']);
230
            unset($options['shard_choser_service']);
231
        }
232
233
        foreach ([
234
            'options' => 'driverOptions',
235
            'driver_class' => 'driverClass',
236
            'wrapper_class' => 'wrapperClass',
237
            'keep_slave' => 'keepSlave',
238
            'shard_choser' => 'shardChoser',
239
            'shard_manager_class' => 'shardManagerClass',
240
            'server_version' => 'serverVersion',
241
            'default_table_options' => 'defaultTableOptions',
242
        ] as $old => $new) {
243
            if (! isset($options[$old])) {
244
                continue;
245
            }
246
247
            $options[$new] = $options[$old];
248
            unset($options[$old]);
249
        }
250
251
        if (! empty($options['slaves']) && ! empty($options['shards'])) {
252
            throw new InvalidArgumentException('Sharding and master-slave connection cannot be used together');
253
        }
254
255
        if (! empty($options['slaves'])) {
256
            $nonRewrittenKeys = [
257
                'driver' => true,
258
                'driverOptions' => true,
259
                'driverClass' => true,
260
                'wrapperClass' => true,
261
                'keepSlave' => true,
262
                'shardChoser' => true,
263
                'platform' => true,
264
                'slaves' => true,
265
                'master' => true,
266
                'shards' => true,
267
                'serverVersion' => true,
268
                'defaultTableOptions' => true,
269
                // included by safety but should have been unset already
270
                'logging' => true,
271
                'profiling' => true,
272
                'mapping_types' => true,
273
                'platform_service' => true,
274
            ];
275 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...
276
                if (isset($nonRewrittenKeys[$key])) {
277
                    continue;
278
                }
279
                $options['master'][$key] = $value;
280
                unset($options[$key]);
281
            }
282
            if (empty($options['wrapperClass'])) {
283
                // Change the wrapper class only if the user does not already forced using a custom one.
284
                $options['wrapperClass'] = 'Doctrine\\DBAL\\Connections\\MasterSlaveConnection';
285
            }
286
        } else {
287
            unset($options['slaves']);
288
        }
289
290
        if (! empty($options['shards'])) {
291
            $nonRewrittenKeys = [
292
                'driver' => true,
293
                'driverOptions' => true,
294
                'driverClass' => true,
295
                'wrapperClass' => true,
296
                'keepSlave' => true,
297
                'shardChoser' => true,
298
                'platform' => true,
299
                'slaves' => true,
300
                'global' => true,
301
                'shards' => true,
302
                'serverVersion' => true,
303
                'defaultTableOptions' => true,
304
                // included by safety but should have been unset already
305
                'logging' => true,
306
                'profiling' => true,
307
                'mapping_types' => true,
308
                'platform_service' => true,
309
            ];
310 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...
311
                if (isset($nonRewrittenKeys[$key])) {
312
                    continue;
313
                }
314
                $options['global'][$key] = $value;
315
                unset($options[$key]);
316
            }
317
            if (empty($options['wrapperClass'])) {
318
                // Change the wrapper class only if the user does not already forced using a custom one.
319
                $options['wrapperClass'] = 'Doctrine\\DBAL\\Sharding\\PoolingShardConnection';
320
            }
321
            if (empty($options['shardManagerClass'])) {
322
                // Change the shard manager class only if the user does not already forced using a custom one.
323
                $options['shardManagerClass'] = 'Doctrine\\DBAL\\Sharding\\PoolingShardManager';
324
            }
325
        } else {
326
            unset($options['shards']);
327
        }
328
329
        return $options;
330
    }
331
332
    /**
333
     * Loads the Doctrine ORM configuration.
334
     *
335
     * Usage example:
336
     *
337
     *     <doctrine:orm id="mydm" connection="myconn" />
338
     *
339
     * @param array            $config    An array of configuration settings
340
     * @param ContainerBuilder $container A ContainerBuilder instance
341
     */
342
    protected function ormLoad(array $config, ContainerBuilder $container)
343
    {
344
        $loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
345
        $loader->load('orm.xml');
346
347
        if (class_exists(AbstractType::class)) {
348
            $container->getDefinition('form.type.entity')->addTag('kernel.reset', ['method' => 'reset']);
349
        }
350
351
        $entityManagers = [];
352
        foreach (array_keys($config['entity_managers']) as $name) {
353
            $entityManagers[$name] = sprintf('doctrine.orm.%s_entity_manager', $name);
354
        }
355
        $container->setParameter('doctrine.entity_managers', $entityManagers);
356
357
        if (empty($config['default_entity_manager'])) {
358
            $tmp                              = array_keys($entityManagers);
359
            $config['default_entity_manager'] = reset($tmp);
360
        }
361
        $container->setParameter('doctrine.default_entity_manager', $config['default_entity_manager']);
362
363
        $options = ['auto_generate_proxy_classes', 'proxy_dir', 'proxy_namespace'];
364
        foreach ($options as $key) {
365
            $container->setParameter('doctrine.orm.' . $key, $config[$key]);
366
        }
367
368
        $container->setAlias('doctrine.orm.entity_manager', sprintf('doctrine.orm.%s_entity_manager', $config['default_entity_manager']));
369
        $container->getAlias('doctrine.orm.entity_manager')->setPublic(true);
370
371
        $config['entity_managers'] = $this->fixManagersAutoMappings($config['entity_managers'], $container->getParameter('kernel.bundles'));
372
373
        $loadPropertyInfoExtractor = interface_exists(PropertyInfoExtractorInterface::class)
374
            && class_exists(DoctrineExtractor::class);
375
376
        foreach ($config['entity_managers'] as $name => $entityManager) {
377
            $entityManager['name'] = $name;
378
            $this->loadOrmEntityManager($entityManager, $container);
379
380
            if ($loadPropertyInfoExtractor) {
381
                $this->loadPropertyInfoExtractor($name, $container);
382
            }
383
384
            $this->loadValidatorLoader($name, $container);
385
        }
386
387
        if ($config['resolve_target_entities']) {
388
            $def = $container->findDefinition('doctrine.orm.listeners.resolve_target_entity');
389
            foreach ($config['resolve_target_entities'] as $name => $implementation) {
390
                $def->addMethodCall('addResolveTargetEntity', [
391
                    $name,
392
                    $implementation,
393
                    [],
394
                ]);
395
            }
396
397
            $def->addTag('doctrine.event_subscriber');
398
        }
399
400
        $container->registerForAutoconfiguration(ServiceEntityRepositoryInterface::class)
401
            ->addTag(ServiceRepositoryCompilerPass::REPOSITORY_SERVICE_TAG);
402
    }
403
404
    /**
405
     * Loads a configured ORM entity manager.
406
     *
407
     * @param array            $entityManager A configured ORM entity manager.
408
     * @param ContainerBuilder $container     A ContainerBuilder instance
409
     */
410
    protected function loadOrmEntityManager(array $entityManager, ContainerBuilder $container)
411
    {
412
        $ormConfigDef = $container->setDefinition(sprintf('doctrine.orm.%s_configuration', $entityManager['name']), new ChildDefinition('doctrine.orm.configuration'));
413
414
        $this->loadOrmEntityManagerMappingInformation($entityManager, $ormConfigDef, $container);
415
        $this->loadOrmCacheDrivers($entityManager, $container);
416
417
        if (isset($entityManager['entity_listener_resolver']) && $entityManager['entity_listener_resolver']) {
418
            $container->setAlias(sprintf('doctrine.orm.%s_entity_listener_resolver', $entityManager['name']), $entityManager['entity_listener_resolver']);
419
        } else {
420
            $definition = new Definition('%doctrine.orm.entity_listener_resolver.class%');
421
            $definition->addArgument(new Reference('service_container'));
422
            $container->setDefinition(sprintf('doctrine.orm.%s_entity_listener_resolver', $entityManager['name']), $definition);
423
        }
424
425
        $methods = [
426
            'setMetadataCacheImpl' => new Reference(sprintf('doctrine.orm.%s_metadata_cache', $entityManager['name'])),
427
            'setQueryCacheImpl' => new Reference(sprintf('doctrine.orm.%s_query_cache', $entityManager['name'])),
428
            'setResultCacheImpl' => new Reference(sprintf('doctrine.orm.%s_result_cache', $entityManager['name'])),
429
            'setMetadataDriverImpl' => new Reference('doctrine.orm.' . $entityManager['name'] . '_metadata_driver'),
430
            'setProxyDir' => '%doctrine.orm.proxy_dir%',
431
            'setProxyNamespace' => '%doctrine.orm.proxy_namespace%',
432
            'setAutoGenerateProxyClasses' => '%doctrine.orm.auto_generate_proxy_classes%',
433
            'setClassMetadataFactoryName' => $entityManager['class_metadata_factory_name'],
434
            'setDefaultRepositoryClassName' => $entityManager['default_repository_class'],
435
            'setNamingStrategy' => new Reference($entityManager['naming_strategy']),
436
            'setQuoteStrategy' => new Reference($entityManager['quote_strategy']),
437
            'setEntityListenerResolver' => new Reference(sprintf('doctrine.orm.%s_entity_listener_resolver', $entityManager['name'])),
438
        ];
439
440
        $listenerId        = sprintf('doctrine.orm.%s_listeners.attach_entity_listeners', $entityManager['name']);
441
        $listenerDef       = $container->setDefinition($listenerId, new Definition('%doctrine.orm.listeners.attach_entity_listeners.class%'));
442
        $listenerTagParams = ['event' => 'loadClassMetadata'];
443
        if (isset($entityManager['connection'])) {
444
            $listenerTagParams['connection'] = $entityManager['connection'];
445
        }
446
        $listenerDef->addTag('doctrine.event_listener', $listenerTagParams);
447
448
        if (isset($entityManager['second_level_cache'])) {
449
            $this->loadOrmSecondLevelCache($entityManager, $ormConfigDef, $container);
450
        }
451
452
        if ($entityManager['repository_factory']) {
453
            $methods['setRepositoryFactory'] = new Reference($entityManager['repository_factory']);
454
        }
455
456
        foreach ($methods as $method => $arg) {
457
            $ormConfigDef->addMethodCall($method, [$arg]);
458
        }
459
460
        foreach ($entityManager['hydrators'] as $name => $class) {
461
            $ormConfigDef->addMethodCall('addCustomHydrationMode', [$name, $class]);
462
        }
463
464
        if (! empty($entityManager['dql'])) {
465
            foreach ($entityManager['dql']['string_functions'] as $name => $function) {
466
                $ormConfigDef->addMethodCall('addCustomStringFunction', [$name, $function]);
467
            }
468
            foreach ($entityManager['dql']['numeric_functions'] as $name => $function) {
469
                $ormConfigDef->addMethodCall('addCustomNumericFunction', [$name, $function]);
470
            }
471
            foreach ($entityManager['dql']['datetime_functions'] as $name => $function) {
472
                $ormConfigDef->addMethodCall('addCustomDatetimeFunction', [$name, $function]);
473
            }
474
        }
475
476
        $enabledFilters    = [];
477
        $filtersParameters = [];
478
        foreach ($entityManager['filters'] as $name => $filter) {
479
            $ormConfigDef->addMethodCall('addFilter', [$name, $filter['class']]);
480
            if ($filter['enabled']) {
481
                $enabledFilters[] = $name;
482
            }
483
            if (! $filter['parameters']) {
484
                continue;
485
            }
486
487
            $filtersParameters[$name] = $filter['parameters'];
488
        }
489
490
        $managerConfiguratorName = sprintf('doctrine.orm.%s_manager_configurator', $entityManager['name']);
491
        $container
492
            ->setDefinition($managerConfiguratorName, new ChildDefinition('doctrine.orm.manager_configurator.abstract'))
493
            ->replaceArgument(0, $enabledFilters)
494
            ->replaceArgument(1, $filtersParameters);
495
496
        if (! isset($entityManager['connection'])) {
497
            $entityManager['connection'] = $this->defaultConnection;
498
        }
499
500
        $container
501
            ->setDefinition(sprintf('doctrine.orm.%s_entity_manager', $entityManager['name']), new ChildDefinition('doctrine.orm.entity_manager.abstract'))
502
            ->setPublic(true)
503
            ->setArguments([
504
                new Reference(sprintf('doctrine.dbal.%s_connection', $entityManager['connection'])),
505
                new Reference(sprintf('doctrine.orm.%s_configuration', $entityManager['name'])),
506
            ])
507
            ->setConfigurator([new Reference($managerConfiguratorName), 'configure']);
508
509
        $container->setAlias(
510
            sprintf('doctrine.orm.%s_entity_manager.event_manager', $entityManager['name']),
511
            new Alias(sprintf('doctrine.dbal.%s_connection.event_manager', $entityManager['connection']), false)
512
        );
513
514
        if (! isset($entityManager['entity_listeners'])) {
515
            return;
516
        }
517
518
        if (! isset($listenerDef)) {
519
            throw new InvalidArgumentException('Entity listeners configuration requires doctrine-orm 2.5.0 or newer');
520
        }
521
522
        $entities = $entityManager['entity_listeners']['entities'];
523
524
        foreach ($entities as $entityListenerClass => $entity) {
525
            foreach ($entity['listeners'] as $listenerClass => $listener) {
526
                foreach ($listener['events'] as $listenerEvent) {
527
                    $listenerEventName = $listenerEvent['type'];
528
                    $listenerMethod    = $listenerEvent['method'];
529
530
                    $listenerDef->addMethodCall('addEntityListener', [
531
                        $entityListenerClass,
532
                        $listenerClass,
533
                        $listenerEventName,
534
                        $listenerMethod,
535
                    ]);
536
                }
537
            }
538
        }
539
    }
540
541
    /**
542
     * Loads an ORM entity managers bundle mapping information.
543
     *
544
     * There are two distinct configuration possibilities for mapping information:
545
     *
546
     * 1. Specify a bundle and optionally details where the entity and mapping information reside.
547
     * 2. Specify an arbitrary mapping location.
548
     *
549
     * @param array            $entityManager A configured ORM entity manager
550
     * @param Definition       $ormConfigDef  A Definition instance
551
     * @param ContainerBuilder $container     A ContainerBuilder instance
552
     *
553
     * @example
554
     *
555
     *  doctrine.orm:
556
     *     mappings:
557
     *         MyBundle1: ~
558
     *         MyBundle2: yml
559
     *         MyBundle3: { type: annotation, dir: Entities/ }
560
     *         MyBundle4: { type: xml, dir: Resources/config/doctrine/mapping }
561
     *         MyBundle5:
562
     *             type: yml
563
     *             dir: bundle-mappings/
564
     *             alias: BundleAlias
565
     *         arbitrary_key:
566
     *             type: xml
567
     *             dir: %kernel.root_dir%/../src/vendor/DoctrineExtensions/lib/DoctrineExtensions/Entities
568
     *             prefix: DoctrineExtensions\Entities\
569
     *             alias: DExt
570
     *
571
     * In the case of bundles everything is really optional (which leads to autodetection for this bundle) but
572
     * in the mappings key everything except alias is a required argument.
573
     */
574
    protected function loadOrmEntityManagerMappingInformation(array $entityManager, Definition $ormConfigDef, ContainerBuilder $container)
575
    {
576
        // reset state of drivers and alias map. They are only used by this methods and children.
577
        $this->drivers  = [];
578
        $this->aliasMap = [];
579
580
        $this->loadMappingInformation($entityManager, $container);
581
        $this->registerMappingDrivers($entityManager, $container);
582
583
        $ormConfigDef->addMethodCall('setEntityNamespaces', [$this->aliasMap]);
584
    }
585
586
    /**
587
     * Loads an ORM second level cache bundle mapping information.
588
     *
589
     * @param array            $entityManager A configured ORM entity manager
590
     * @param Definition       $ormConfigDef  A Definition instance
591
     * @param ContainerBuilder $container     A ContainerBuilder instance
592
     *
593
     * @example
594
     *  entity_managers:
595
     *      default:
596
     *          second_level_cache:
597
     *              region_cache_driver: apc
598
     *              log_enabled: true
599
     *              regions:
600
     *                  my_service_region:
601
     *                      type: service
602
     *                      service : "my_service_region"
603
     *
604
     *                  my_query_region:
605
     *                      lifetime: 300
606
     *                      cache_driver: array
607
     *                      type: filelock
608
     *
609
     *                  my_entity_region:
610
     *                      lifetime: 600
611
     *                      cache_driver:
612
     *                          type: apc
613
     */
614
    protected function loadOrmSecondLevelCache(array $entityManager, Definition $ormConfigDef, ContainerBuilder $container)
615
    {
616
        $driverId = null;
617
        $enabled  = $entityManager['second_level_cache']['enabled'];
618
619
        if (isset($entityManager['second_level_cache']['region_cache_driver'])) {
620
            $driverName = 'second_level_cache.region_cache_driver';
621
            $driverMap  = $entityManager['second_level_cache']['region_cache_driver'];
622
            $driverId   = $this->loadCacheDriver($driverName, $entityManager['name'], $driverMap, $container);
623
        }
624
625
        $configId   = sprintf('doctrine.orm.%s_second_level_cache.cache_configuration', $entityManager['name']);
626
        $regionsId  = sprintf('doctrine.orm.%s_second_level_cache.regions_configuration', $entityManager['name']);
627
        $driverId   = $driverId ?: sprintf('doctrine.orm.%s_second_level_cache.region_cache_driver', $entityManager['name']);
628
        $configDef  = $container->setDefinition($configId, new Definition('%doctrine.orm.second_level_cache.cache_configuration.class%'));
629
        $regionsDef = $container->setDefinition($regionsId, new Definition('%doctrine.orm.second_level_cache.regions_configuration.class%'));
630
631
        $slcFactoryId = sprintf('doctrine.orm.%s_second_level_cache.default_cache_factory', $entityManager['name']);
632
        $factoryClass = isset($entityManager['second_level_cache']['factory']) ? $entityManager['second_level_cache']['factory'] : '%doctrine.orm.second_level_cache.default_cache_factory.class%';
633
634
        $definition = new Definition($factoryClass, [new Reference($regionsId), new Reference($driverId)]);
635
636
        $slcFactoryDef = $container
637
            ->setDefinition($slcFactoryId, $definition);
638
639
        if (isset($entityManager['second_level_cache']['regions'])) {
640
            foreach ($entityManager['second_level_cache']['regions'] as $name => $region) {
641
                $regionRef  = null;
642
                $regionType = $region['type'];
643
644
                if ($regionType === 'service') {
645
                    $regionId  = sprintf('doctrine.orm.%s_second_level_cache.region.%s', $entityManager['name'], $name);
646
                    $regionRef = new Reference($region['service']);
647
648
                    $container->setAlias($regionId, new Alias($region['service'], false));
649
                }
650
651
                if ($regionType === 'default' || $regionType === 'filelock') {
652
                    $regionId   = sprintf('doctrine.orm.%s_second_level_cache.region.%s', $entityManager['name'], $name);
653
                    $driverName = sprintf('second_level_cache.region.%s_driver', $name);
654
                    $driverMap  = $region['cache_driver'];
655
                    $driverId   = $this->loadCacheDriver($driverName, $entityManager['name'], $driverMap, $container);
656
                    $regionRef  = new Reference($regionId);
657
658
                    $container
659
                        ->setDefinition($regionId, new Definition('%doctrine.orm.second_level_cache.default_region.class%'))
660
                        ->setArguments([$name, new Reference($driverId), $region['lifetime']]);
661
                }
662
663
                if ($regionType === 'filelock') {
664
                    $regionId = sprintf('doctrine.orm.%s_second_level_cache.region.%s_filelock', $entityManager['name'], $name);
665
666
                    $container
667
                        ->setDefinition($regionId, new Definition('%doctrine.orm.second_level_cache.filelock_region.class%'))
668
                        ->setArguments([$regionRef, $region['lock_path'], $region['lock_lifetime']]);
669
670
                    $regionRef = new Reference($regionId);
671
                    $regionsDef->addMethodCall('getLockLifetime', [$name, $region['lock_lifetime']]);
672
                }
673
674
                $regionsDef->addMethodCall('setLifetime', [$name, $region['lifetime']]);
675
                $slcFactoryDef->addMethodCall('setRegion', [$regionRef]);
676
            }
677
        }
678
679
        if ($entityManager['second_level_cache']['log_enabled']) {
680
            $loggerChainId   = sprintf('doctrine.orm.%s_second_level_cache.logger_chain', $entityManager['name']);
681
            $loggerStatsId   = sprintf('doctrine.orm.%s_second_level_cache.logger_statistics', $entityManager['name']);
682
            $loggerChaingDef = $container->setDefinition($loggerChainId, new Definition('%doctrine.orm.second_level_cache.logger_chain.class%'));
683
            $loggerStatsDef  = $container->setDefinition($loggerStatsId, new Definition('%doctrine.orm.second_level_cache.logger_statistics.class%'));
684
685
            $loggerChaingDef->addMethodCall('setLogger', ['statistics', $loggerStatsDef]);
686
            $configDef->addMethodCall('setCacheLogger', [$loggerChaingDef]);
687
688
            foreach ($entityManager['second_level_cache']['loggers'] as $name => $logger) {
689
                $loggerId  = sprintf('doctrine.orm.%s_second_level_cache.logger.%s', $entityManager['name'], $name);
690
                $loggerRef = new Reference($logger['service']);
691
692
                $container->setAlias($loggerId, new Alias($logger['service'], false));
693
                $loggerChaingDef->addMethodCall('setLogger', [$name, $loggerRef]);
694
            }
695
        }
696
697
        $configDef->addMethodCall('setCacheFactory', [$slcFactoryDef]);
698
        $configDef->addMethodCall('setRegionsConfiguration', [$regionsDef]);
699
        $ormConfigDef->addMethodCall('setSecondLevelCacheEnabled', [$enabled]);
700
        $ormConfigDef->addMethodCall('setSecondLevelCacheConfiguration', [$configDef]);
701
    }
702
703
    /**
704
     * {@inheritDoc}
705
     */
706
    protected function getObjectManagerElementName($name) : string
707
    {
708
        return 'doctrine.orm.' . $name;
709
    }
710
711
    protected function getMappingObjectDefaultName() : string
712
    {
713
        return 'Entity';
714
    }
715
716
    /**
717
     * {@inheritDoc}
718
     */
719
    protected function getMappingResourceConfigDirectory() : string
720
    {
721
        return 'Resources/config/doctrine';
722
    }
723
724
    /**
725
     * {@inheritDoc}
726
     */
727
    protected function getMappingResourceExtension() : string
728
    {
729
        return 'orm';
730
    }
731
732
    /**
733
     * {@inheritDoc}
734
     */
735
    protected function loadCacheDriver($cacheName, $objectManagerName, array $cacheDriver, ContainerBuilder $container) : string
736
    {
737
        $serviceId = null;
738
        $aliasId   = $this->getObjectManagerElementName(sprintf('%s_%s', $objectManagerName, $cacheName));
739
740
        if ($cacheDriver['type'] === null) {
741
            $cacheDriver = [
742
                'type' => 'pool',
743
                'pool' => $this->getPoolNameForCacheDriver($cacheName),
744
            ];
745
        }
746
747
        switch ($cacheDriver['type']) {
748
            case 'service':
749
                $serviceId = $cacheDriver['id'];
750
                break;
751
752
            case 'pool':
753
                $serviceId = $this->createPoolCacheDefinition($container, $cacheDriver['pool']);
754
                break;
755
756
            case 'provider':
757
                $serviceId = sprintf('doctrine_cache.providers.%s', $cacheDriver['cache_provider']);
758
                break;
759
        }
760
761
        if ($serviceId !== null) {
762
            $container->setAlias($aliasId, new Alias($serviceId, false));
763
764
            return $aliasId;
765
        }
766
767
        return $this->adapter->loadCacheDriver($cacheName, $objectManagerName, $cacheDriver, $container);
768
    }
769
770
    /**
771
     * Loads a configured entity managers cache drivers.
772
     *
773
     * @param array            $entityManager A configured ORM entity manager.
774
     * @param ContainerBuilder $container     A ContainerBuilder instance
775
     */
776
    protected function loadOrmCacheDrivers(array $entityManager, ContainerBuilder $container)
777
    {
778
        $this->loadCacheDriver('metadata_cache', $entityManager['name'], $entityManager['metadata_cache_driver'], $container);
779
        $this->loadCacheDriver('result_cache', $entityManager['name'], $entityManager['result_cache_driver'], $container);
780
        $this->loadCacheDriver('query_cache', $entityManager['name'], $entityManager['query_cache_driver'], $container);
781
    }
782
783
    /**
784
     * Loads a property info extractor for each defined entity manager.
785
     *
786
     * @param string $entityManagerName
787
     */
788
    private function loadPropertyInfoExtractor($entityManagerName, ContainerBuilder $container)
789
    {
790
        $propertyExtractorDefinition = $container->register(sprintf('doctrine.orm.%s_entity_manager.property_info_extractor', $entityManagerName), DoctrineExtractor::class);
791
        if (property_exists(DoctrineExtractor::class, 'entityManager')) {
792
            $argumentId = sprintf('doctrine.orm.%s_entity_manager', $entityManagerName);
793
        } else {
794
            $argumentId = sprintf('doctrine.orm.%s_entity_manager.metadata_factory', $entityManagerName);
795
796
            $metadataFactoryDefinition = $container->register($argumentId, ClassMetadataFactory::class);
797
            $metadataFactoryDefinition->setFactory([
798
                new Reference(sprintf('doctrine.orm.%s_entity_manager', $entityManagerName)),
799
                'getMetadataFactory',
800
            ]);
801
            $metadataFactoryDefinition->setPublic(false);
802
        }
803
804
        $propertyExtractorDefinition->addArgument(new Reference($argumentId));
805
806
        $propertyExtractorDefinition->addTag('property_info.list_extractor', ['priority' => -1001]);
807
        $propertyExtractorDefinition->addTag('property_info.type_extractor', ['priority' => -999]);
808
809
        if (! is_a(DoctrineExtractor::class, PropertyAccessExtractorInterface::class, true)) {
810
            return;
811
        }
812
813
        $propertyExtractorDefinition->addTag('property_info.access_extractor', ['priority' => -999]);
814
    }
815
816
    /**
817
     * Loads a validator loader for each defined entity manager.
818
     */
819
    private function loadValidatorLoader(string $entityManagerName, ContainerBuilder $container) : void
820
    {
821
        if (! interface_exists(LoaderInterface::class) || ! class_exists(DoctrineLoader::class)) {
822
            return;
823
        }
824
825
        $validatorLoaderDefinition = $container->register(sprintf('doctrine.orm.%s_entity_manager.validator_loader', $entityManagerName), DoctrineLoader::class);
826
        $validatorLoaderDefinition->addArgument(new Reference(sprintf('doctrine.orm.%s_entity_manager', $entityManagerName)));
827
828
        $validatorLoaderDefinition->addTag('validator.auto_mapper', ['priority' => -100]);
829
    }
830
831
    /**
832
     * @param array  $objectManager
833
     * @param string $cacheName
834
     */
835
    public function loadObjectManagerCacheDriver(array $objectManager, ContainerBuilder $container, $cacheName)
836
    {
837
        $this->loadCacheDriver($cacheName, $objectManager['name'], $objectManager[$cacheName . '_driver'], $container);
838
    }
839
840
    /**
841
     * {@inheritDoc}
842
     */
843
    public function getXsdValidationBasePath() : string
844
    {
845
        return __DIR__ . '/../Resources/config/schema';
846
    }
847
848
    /**
849
     * {@inheritDoc}
850
     */
851
    public function getNamespace() : string
852
    {
853
        return 'http://symfony.com/schema/dic/doctrine';
854
    }
855
856
    /**
857
     * {@inheritDoc}
858
     */
859
    public function getConfiguration(array $config, ContainerBuilder $container) : Configuration
860
    {
861
        return new Configuration($container->getParameter('kernel.debug'));
862
    }
863
864
    private function loadMessengerServices(ContainerBuilder $container) : void
865
    {
866
        // If the Messenger component is installed and the doctrine transaction middleware is available, wire it:
867
        if (! interface_exists(MessageBusInterface::class) || ! class_exists(DoctrineTransactionMiddleware::class)) {
868
            return;
869
        }
870
871
        $loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
872
        $loader->load('messenger.xml');
873
874
        if (! class_exists(DoctrineTransportFactory::class)) {
875
            return;
876
        }
877
878
        $transportFactoryDefinition = $container->getDefinition('messenger.transport.doctrine.factory');
879
        $transportFactoryDefinition->addTag('messenger.transport_factory');
880
    }
881
882
    private function createPoolCacheDefinition(ContainerBuilder $container, string $poolName) : string
883
    {
884
        if (! class_exists(DoctrineProvider::class)) {
885
            throw new LogicException('Using the "pool" cache type is only supported when symfony/cache is installed.');
886
        }
887
888
        $serviceId = sprintf('doctrine.orm.cache.pool.%s', $poolName);
889
890
        $definition = $container->register($serviceId, DoctrineProvider::class);
891
        $definition->addArgument(new Reference($poolName));
892
        $definition->setPrivate(true);
893
894
        return $serviceId;
895
    }
896
897
    private function getPoolNameForCacheDriver(string $driverName) : string
898
    {
899
        switch ($driverName) {
900
            case 'metadata_cache':
901
                return 'cache.system';
902
            default:
903
                return 'cache.app';
904
        }
905
    }
906
}
907