Completed
Push — master ( baba12...502b0f )
by Andreas
08:34
created

DoctrineExtension::getConfiguration()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
rs 10
cc 1
nc 1
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
            return;
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 (! 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->ormLoad($config['orm'], $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
        $this->loadMessengerServices($container);
404
    }
405
406
    /**
407
     * Loads a configured ORM entity manager.
408
     *
409
     * @param array            $entityManager A configured ORM entity manager.
410
     * @param ContainerBuilder $container     A ContainerBuilder instance
411
     */
412
    protected function loadOrmEntityManager(array $entityManager, ContainerBuilder $container)
413
    {
414
        $ormConfigDef = $container->setDefinition(sprintf('doctrine.orm.%s_configuration', $entityManager['name']), new ChildDefinition('doctrine.orm.configuration'));
415
416
        $this->loadOrmEntityManagerMappingInformation($entityManager, $ormConfigDef, $container);
417
        $this->loadOrmCacheDrivers($entityManager, $container);
418
419
        if (isset($entityManager['entity_listener_resolver']) && $entityManager['entity_listener_resolver']) {
420
            $container->setAlias(sprintf('doctrine.orm.%s_entity_listener_resolver', $entityManager['name']), $entityManager['entity_listener_resolver']);
421
        } else {
422
            $definition = new Definition('%doctrine.orm.entity_listener_resolver.class%');
423
            $definition->addArgument(new Reference('service_container'));
424
            $container->setDefinition(sprintf('doctrine.orm.%s_entity_listener_resolver', $entityManager['name']), $definition);
425
        }
426
427
        $methods = [
428
            'setMetadataCacheImpl' => new Reference(sprintf('doctrine.orm.%s_metadata_cache', $entityManager['name'])),
429
            'setQueryCacheImpl' => new Reference(sprintf('doctrine.orm.%s_query_cache', $entityManager['name'])),
430
            'setResultCacheImpl' => new Reference(sprintf('doctrine.orm.%s_result_cache', $entityManager['name'])),
431
            'setMetadataDriverImpl' => new Reference('doctrine.orm.' . $entityManager['name'] . '_metadata_driver'),
432
            'setProxyDir' => '%doctrine.orm.proxy_dir%',
433
            'setProxyNamespace' => '%doctrine.orm.proxy_namespace%',
434
            'setAutoGenerateProxyClasses' => '%doctrine.orm.auto_generate_proxy_classes%',
435
            'setClassMetadataFactoryName' => $entityManager['class_metadata_factory_name'],
436
            'setDefaultRepositoryClassName' => $entityManager['default_repository_class'],
437
            'setNamingStrategy' => new Reference($entityManager['naming_strategy']),
438
            'setQuoteStrategy' => new Reference($entityManager['quote_strategy']),
439
            'setEntityListenerResolver' => new Reference(sprintf('doctrine.orm.%s_entity_listener_resolver', $entityManager['name'])),
440
        ];
441
442
        $listenerId        = sprintf('doctrine.orm.%s_listeners.attach_entity_listeners', $entityManager['name']);
443
        $listenerDef       = $container->setDefinition($listenerId, new Definition('%doctrine.orm.listeners.attach_entity_listeners.class%'));
444
        $listenerTagParams = ['event' => 'loadClassMetadata'];
445
        if (isset($entityManager['connection'])) {
446
            $listenerTagParams['connection'] = $entityManager['connection'];
447
        }
448
        $listenerDef->addTag('doctrine.event_listener', $listenerTagParams);
449
450
        if (isset($entityManager['second_level_cache'])) {
451
            $this->loadOrmSecondLevelCache($entityManager, $ormConfigDef, $container);
452
        }
453
454
        if ($entityManager['repository_factory']) {
455
            $methods['setRepositoryFactory'] = new Reference($entityManager['repository_factory']);
456
        }
457
458
        foreach ($methods as $method => $arg) {
459
            $ormConfigDef->addMethodCall($method, [$arg]);
460
        }
461
462
        foreach ($entityManager['hydrators'] as $name => $class) {
463
            $ormConfigDef->addMethodCall('addCustomHydrationMode', [$name, $class]);
464
        }
465
466
        if (! empty($entityManager['dql'])) {
467
            foreach ($entityManager['dql']['string_functions'] as $name => $function) {
468
                $ormConfigDef->addMethodCall('addCustomStringFunction', [$name, $function]);
469
            }
470
            foreach ($entityManager['dql']['numeric_functions'] as $name => $function) {
471
                $ormConfigDef->addMethodCall('addCustomNumericFunction', [$name, $function]);
472
            }
473
            foreach ($entityManager['dql']['datetime_functions'] as $name => $function) {
474
                $ormConfigDef->addMethodCall('addCustomDatetimeFunction', [$name, $function]);
475
            }
476
        }
477
478
        $enabledFilters    = [];
479
        $filtersParameters = [];
480
        foreach ($entityManager['filters'] as $name => $filter) {
481
            $ormConfigDef->addMethodCall('addFilter', [$name, $filter['class']]);
482
            if ($filter['enabled']) {
483
                $enabledFilters[] = $name;
484
            }
485
            if (! $filter['parameters']) {
486
                continue;
487
            }
488
489
            $filtersParameters[$name] = $filter['parameters'];
490
        }
491
492
        $managerConfiguratorName = sprintf('doctrine.orm.%s_manager_configurator', $entityManager['name']);
493
        $container
494
            ->setDefinition($managerConfiguratorName, new ChildDefinition('doctrine.orm.manager_configurator.abstract'))
495
            ->replaceArgument(0, $enabledFilters)
496
            ->replaceArgument(1, $filtersParameters);
497
498
        if (! isset($entityManager['connection'])) {
499
            $entityManager['connection'] = $this->defaultConnection;
500
        }
501
502
        $container
503
            ->setDefinition(sprintf('doctrine.orm.%s_entity_manager', $entityManager['name']), new ChildDefinition('doctrine.orm.entity_manager.abstract'))
504
            ->setPublic(true)
505
            ->setArguments([
506
                new Reference(sprintf('doctrine.dbal.%s_connection', $entityManager['connection'])),
507
                new Reference(sprintf('doctrine.orm.%s_configuration', $entityManager['name'])),
508
            ])
509
            ->setConfigurator([new Reference($managerConfiguratorName), 'configure']);
510
511
        $container->setAlias(
512
            sprintf('doctrine.orm.%s_entity_manager.event_manager', $entityManager['name']),
513
            new Alias(sprintf('doctrine.dbal.%s_connection.event_manager', $entityManager['connection']), false)
514
        );
515
516
        if (! isset($entityManager['entity_listeners'])) {
517
            return;
518
        }
519
520
        if (! isset($listenerDef)) {
521
            throw new InvalidArgumentException('Entity listeners configuration requires doctrine-orm 2.5.0 or newer');
522
        }
523
524
        $entities = $entityManager['entity_listeners']['entities'];
525
526
        foreach ($entities as $entityListenerClass => $entity) {
527
            foreach ($entity['listeners'] as $listenerClass => $listener) {
528
                foreach ($listener['events'] as $listenerEvent) {
529
                    $listenerEventName = $listenerEvent['type'];
530
                    $listenerMethod    = $listenerEvent['method'];
531
532
                    $listenerDef->addMethodCall('addEntityListener', [
533
                        $entityListenerClass,
534
                        $listenerClass,
535
                        $listenerEventName,
536
                        $listenerMethod,
537
                    ]);
538
                }
539
            }
540
        }
541
    }
542
543
    /**
544
     * Loads an ORM entity managers bundle mapping information.
545
     *
546
     * There are two distinct configuration possibilities for mapping information:
547
     *
548
     * 1. Specify a bundle and optionally details where the entity and mapping information reside.
549
     * 2. Specify an arbitrary mapping location.
550
     *
551
     * @param array            $entityManager A configured ORM entity manager
552
     * @param Definition       $ormConfigDef  A Definition instance
553
     * @param ContainerBuilder $container     A ContainerBuilder instance
554
     *
555
     * @example
556
     *
557
     *  doctrine.orm:
558
     *     mappings:
559
     *         MyBundle1: ~
560
     *         MyBundle2: yml
561
     *         MyBundle3: { type: annotation, dir: Entities/ }
562
     *         MyBundle4: { type: xml, dir: Resources/config/doctrine/mapping }
563
     *         MyBundle5:
564
     *             type: yml
565
     *             dir: bundle-mappings/
566
     *             alias: BundleAlias
567
     *         arbitrary_key:
568
     *             type: xml
569
     *             dir: %kernel.root_dir%/../src/vendor/DoctrineExtensions/lib/DoctrineExtensions/Entities
570
     *             prefix: DoctrineExtensions\Entities\
571
     *             alias: DExt
572
     *
573
     * In the case of bundles everything is really optional (which leads to autodetection for this bundle) but
574
     * in the mappings key everything except alias is a required argument.
575
     */
576
    protected function loadOrmEntityManagerMappingInformation(array $entityManager, Definition $ormConfigDef, ContainerBuilder $container)
577
    {
578
        // reset state of drivers and alias map. They are only used by this methods and children.
579
        $this->drivers  = [];
580
        $this->aliasMap = [];
581
582
        $this->loadMappingInformation($entityManager, $container);
583
        $this->registerMappingDrivers($entityManager, $container);
584
585
        $ormConfigDef->addMethodCall('setEntityNamespaces', [$this->aliasMap]);
586
    }
587
588
    /**
589
     * Loads an ORM second level cache bundle mapping information.
590
     *
591
     * @param array            $entityManager A configured ORM entity manager
592
     * @param Definition       $ormConfigDef  A Definition instance
593
     * @param ContainerBuilder $container     A ContainerBuilder instance
594
     *
595
     * @example
596
     *  entity_managers:
597
     *      default:
598
     *          second_level_cache:
599
     *              region_cache_driver: apc
600
     *              log_enabled: true
601
     *              regions:
602
     *                  my_service_region:
603
     *                      type: service
604
     *                      service : "my_service_region"
605
     *
606
     *                  my_query_region:
607
     *                      lifetime: 300
608
     *                      cache_driver: array
609
     *                      type: filelock
610
     *
611
     *                  my_entity_region:
612
     *                      lifetime: 600
613
     *                      cache_driver:
614
     *                          type: apc
615
     */
616
    protected function loadOrmSecondLevelCache(array $entityManager, Definition $ormConfigDef, ContainerBuilder $container)
617
    {
618
        $driverId = null;
619
        $enabled  = $entityManager['second_level_cache']['enabled'];
620
621
        if (isset($entityManager['second_level_cache']['region_cache_driver'])) {
622
            $driverName = 'second_level_cache.region_cache_driver';
623
            $driverMap  = $entityManager['second_level_cache']['region_cache_driver'];
624
            $driverId   = $this->loadCacheDriver($driverName, $entityManager['name'], $driverMap, $container);
625
        }
626
627
        $configId   = sprintf('doctrine.orm.%s_second_level_cache.cache_configuration', $entityManager['name']);
628
        $regionsId  = sprintf('doctrine.orm.%s_second_level_cache.regions_configuration', $entityManager['name']);
629
        $driverId   = $driverId ?: sprintf('doctrine.orm.%s_second_level_cache.region_cache_driver', $entityManager['name']);
630
        $configDef  = $container->setDefinition($configId, new Definition('%doctrine.orm.second_level_cache.cache_configuration.class%'));
631
        $regionsDef = $container->setDefinition($regionsId, new Definition('%doctrine.orm.second_level_cache.regions_configuration.class%'));
632
633
        $slcFactoryId = sprintf('doctrine.orm.%s_second_level_cache.default_cache_factory', $entityManager['name']);
634
        $factoryClass = isset($entityManager['second_level_cache']['factory']) ? $entityManager['second_level_cache']['factory'] : '%doctrine.orm.second_level_cache.default_cache_factory.class%';
635
636
        $definition = new Definition($factoryClass, [new Reference($regionsId), new Reference($driverId)]);
637
638
        $slcFactoryDef = $container
639
            ->setDefinition($slcFactoryId, $definition);
640
641
        if (isset($entityManager['second_level_cache']['regions'])) {
642
            foreach ($entityManager['second_level_cache']['regions'] as $name => $region) {
643
                $regionRef  = null;
644
                $regionType = $region['type'];
645
646
                if ($regionType === 'service') {
647
                    $regionId  = sprintf('doctrine.orm.%s_second_level_cache.region.%s', $entityManager['name'], $name);
648
                    $regionRef = new Reference($region['service']);
649
650
                    $container->setAlias($regionId, new Alias($region['service'], false));
651
                }
652
653
                if ($regionType === 'default' || $regionType === 'filelock') {
654
                    $regionId   = sprintf('doctrine.orm.%s_second_level_cache.region.%s', $entityManager['name'], $name);
655
                    $driverName = sprintf('second_level_cache.region.%s_driver', $name);
656
                    $driverMap  = $region['cache_driver'];
657
                    $driverId   = $this->loadCacheDriver($driverName, $entityManager['name'], $driverMap, $container);
658
                    $regionRef  = new Reference($regionId);
659
660
                    $container
661
                        ->setDefinition($regionId, new Definition('%doctrine.orm.second_level_cache.default_region.class%'))
662
                        ->setArguments([$name, new Reference($driverId), $region['lifetime']]);
663
                }
664
665
                if ($regionType === 'filelock') {
666
                    $regionId = sprintf('doctrine.orm.%s_second_level_cache.region.%s_filelock', $entityManager['name'], $name);
667
668
                    $container
669
                        ->setDefinition($regionId, new Definition('%doctrine.orm.second_level_cache.filelock_region.class%'))
670
                        ->setArguments([$regionRef, $region['lock_path'], $region['lock_lifetime']]);
671
672
                    $regionRef = new Reference($regionId);
673
                    $regionsDef->addMethodCall('getLockLifetime', [$name, $region['lock_lifetime']]);
674
                }
675
676
                $regionsDef->addMethodCall('setLifetime', [$name, $region['lifetime']]);
677
                $slcFactoryDef->addMethodCall('setRegion', [$regionRef]);
678
            }
679
        }
680
681
        if ($entityManager['second_level_cache']['log_enabled']) {
682
            $loggerChainId   = sprintf('doctrine.orm.%s_second_level_cache.logger_chain', $entityManager['name']);
683
            $loggerStatsId   = sprintf('doctrine.orm.%s_second_level_cache.logger_statistics', $entityManager['name']);
684
            $loggerChaingDef = $container->setDefinition($loggerChainId, new Definition('%doctrine.orm.second_level_cache.logger_chain.class%'));
685
            $loggerStatsDef  = $container->setDefinition($loggerStatsId, new Definition('%doctrine.orm.second_level_cache.logger_statistics.class%'));
686
687
            $loggerChaingDef->addMethodCall('setLogger', ['statistics', $loggerStatsDef]);
688
            $configDef->addMethodCall('setCacheLogger', [$loggerChaingDef]);
689
690
            foreach ($entityManager['second_level_cache']['loggers'] as $name => $logger) {
691
                $loggerId  = sprintf('doctrine.orm.%s_second_level_cache.logger.%s', $entityManager['name'], $name);
692
                $loggerRef = new Reference($logger['service']);
693
694
                $container->setAlias($loggerId, new Alias($logger['service'], false));
695
                $loggerChaingDef->addMethodCall('setLogger', [$name, $loggerRef]);
696
            }
697
        }
698
699
        $configDef->addMethodCall('setCacheFactory', [$slcFactoryDef]);
700
        $configDef->addMethodCall('setRegionsConfiguration', [$regionsDef]);
701
        $ormConfigDef->addMethodCall('setSecondLevelCacheEnabled', [$enabled]);
702
        $ormConfigDef->addMethodCall('setSecondLevelCacheConfiguration', [$configDef]);
703
    }
704
705
    /**
706
     * {@inheritDoc}
707
     */
708
    protected function getObjectManagerElementName($name) : string
709
    {
710
        return 'doctrine.orm.' . $name;
711
    }
712
713
    protected function getMappingObjectDefaultName() : string
714
    {
715
        return 'Entity';
716
    }
717
718
    /**
719
     * {@inheritDoc}
720
     */
721
    protected function getMappingResourceConfigDirectory() : string
722
    {
723
        return 'Resources/config/doctrine';
724
    }
725
726
    /**
727
     * {@inheritDoc}
728
     */
729
    protected function getMappingResourceExtension() : string
730
    {
731
        return 'orm';
732
    }
733
734
    /**
735
     * {@inheritDoc}
736
     */
737
    protected function loadCacheDriver($cacheName, $objectManagerName, array $cacheDriver, ContainerBuilder $container) : string
738
    {
739
        $serviceId = null;
740
        $aliasId   = $this->getObjectManagerElementName(sprintf('%s_%s', $objectManagerName, $cacheName));
741
742
        if ($cacheDriver['type'] === null) {
743
            $cacheDriver = [
744
                'type' => 'pool',
745
                'pool' => $this->getPoolNameForCacheDriver($cacheName),
746
            ];
747
        }
748
749
        switch ($cacheDriver['type']) {
750
            case 'service':
751
                $serviceId = $cacheDriver['id'];
752
                break;
753
754
            case 'pool':
755
                $serviceId = $this->createPoolCacheDefinition($container, $cacheDriver['pool']);
756
                break;
757
758
            case 'provider':
759
                $serviceId = sprintf('doctrine_cache.providers.%s', $cacheDriver['cache_provider']);
760
                break;
761
        }
762
763
        if ($serviceId !== null) {
764
            $container->setAlias($aliasId, new Alias($serviceId, false));
765
766
            return $aliasId;
767
        }
768
769
        return $this->adapter->loadCacheDriver($cacheName, $objectManagerName, $cacheDriver, $container);
770
    }
771
772
    /**
773
     * Loads a configured entity managers cache drivers.
774
     *
775
     * @param array            $entityManager A configured ORM entity manager.
776
     * @param ContainerBuilder $container     A ContainerBuilder instance
777
     */
778
    protected function loadOrmCacheDrivers(array $entityManager, ContainerBuilder $container)
779
    {
780
        $this->loadCacheDriver('metadata_cache', $entityManager['name'], $entityManager['metadata_cache_driver'], $container);
781
        $this->loadCacheDriver('result_cache', $entityManager['name'], $entityManager['result_cache_driver'], $container);
782
        $this->loadCacheDriver('query_cache', $entityManager['name'], $entityManager['query_cache_driver'], $container);
783
    }
784
785
    /**
786
     * Loads a property info extractor for each defined entity manager.
787
     *
788
     * @param string $entityManagerName
789
     */
790
    private function loadPropertyInfoExtractor($entityManagerName, ContainerBuilder $container)
791
    {
792
        $propertyExtractorDefinition = $container->register(sprintf('doctrine.orm.%s_entity_manager.property_info_extractor', $entityManagerName), DoctrineExtractor::class);
793
        if (property_exists(DoctrineExtractor::class, 'entityManager')) {
794
            $argumentId = sprintf('doctrine.orm.%s_entity_manager', $entityManagerName);
795
        } else {
796
            $argumentId = sprintf('doctrine.orm.%s_entity_manager.metadata_factory', $entityManagerName);
797
798
            $metadataFactoryDefinition = $container->register($argumentId, ClassMetadataFactory::class);
799
            $metadataFactoryDefinition->setFactory([
800
                new Reference(sprintf('doctrine.orm.%s_entity_manager', $entityManagerName)),
801
                'getMetadataFactory',
802
            ]);
803
            $metadataFactoryDefinition->setPublic(false);
804
        }
805
806
        $propertyExtractorDefinition->addArgument(new Reference($argumentId));
807
808
        $propertyExtractorDefinition->addTag('property_info.list_extractor', ['priority' => -1001]);
809
        $propertyExtractorDefinition->addTag('property_info.type_extractor', ['priority' => -999]);
810
811
        if (! is_a(DoctrineExtractor::class, PropertyAccessExtractorInterface::class, true)) {
812
            return;
813
        }
814
815
        $propertyExtractorDefinition->addTag('property_info.access_extractor', ['priority' => -999]);
816
    }
817
818
    /**
819
     * Loads a validator loader for each defined entity manager.
820
     */
821
    private function loadValidatorLoader(string $entityManagerName, ContainerBuilder $container) : void
822
    {
823
        if (! interface_exists(LoaderInterface::class) || ! class_exists(DoctrineLoader::class)) {
824
            return;
825
        }
826
827
        $validatorLoaderDefinition = $container->register(sprintf('doctrine.orm.%s_entity_manager.validator_loader', $entityManagerName), DoctrineLoader::class);
828
        $validatorLoaderDefinition->addArgument(new Reference(sprintf('doctrine.orm.%s_entity_manager', $entityManagerName)));
829
830
        $validatorLoaderDefinition->addTag('validator.auto_mapper', ['priority' => -100]);
831
    }
832
833
    /**
834
     * @param array  $objectManager
835
     * @param string $cacheName
836
     */
837
    public function loadObjectManagerCacheDriver(array $objectManager, ContainerBuilder $container, $cacheName)
838
    {
839
        $this->loadCacheDriver($cacheName, $objectManager['name'], $objectManager[$cacheName . '_driver'], $container);
840
    }
841
842
    /**
843
     * {@inheritDoc}
844
     */
845
    public function getXsdValidationBasePath() : string
846
    {
847
        return __DIR__ . '/../Resources/config/schema';
848
    }
849
850
    /**
851
     * {@inheritDoc}
852
     */
853
    public function getNamespace() : string
854
    {
855
        return 'http://symfony.com/schema/dic/doctrine';
856
    }
857
858
    /**
859
     * {@inheritDoc}
860
     */
861
    public function getConfiguration(array $config, ContainerBuilder $container) : Configuration
862
    {
863
        return new Configuration($container->getParameter('kernel.debug'));
864
    }
865
866
    private function loadMessengerServices(ContainerBuilder $container) : void
867
    {
868
        // If the Messenger component is installed and the doctrine transaction middleware is available, wire it:
869
        if (! interface_exists(MessageBusInterface::class) || ! class_exists(DoctrineTransactionMiddleware::class)) {
870
            return;
871
        }
872
873
        $loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
874
        $loader->load('messenger.xml');
875
876
        if (! class_exists(DoctrineTransportFactory::class)) {
877
            return;
878
        }
879
880
        $transportFactoryDefinition = $container->getDefinition('messenger.transport.doctrine.factory');
881
        $transportFactoryDefinition->addTag('messenger.transport_factory');
882
    }
883
884
    private function createPoolCacheDefinition(ContainerBuilder $container, string $poolName) : string
885
    {
886
        if (! class_exists(DoctrineProvider::class)) {
887
            throw new LogicException('Using the "pool" cache type is only supported when symfony/cache is installed.');
888
        }
889
890
        $serviceId = sprintf('doctrine.orm.cache.pool.%s', $poolName);
891
892
        $definition = $container->register($serviceId, DoctrineProvider::class);
893
        $definition->addArgument(new Reference($poolName));
894
        $definition->setPrivate(true);
895
896
        return $serviceId;
897
    }
898
899
    private function getPoolNameForCacheDriver(string $driverName) : string
900
    {
901
        switch ($driverName) {
902
            case 'metadata_cache':
903
                return 'cache.system';
904
            default:
905
                return 'cache.app';
906
        }
907
    }
908
}
909