Completed
Pull Request — master (#981)
by Andreas
10:24
created

DoctrineExtension::dbalLoad()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 31

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 31
rs 9.424
cc 4
nc 8
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 const E_USER_DEPRECATED;
33
use function class_exists;
34
use function sprintf;
35
use function trigger_error;
36
37
/**
38
 * DoctrineExtension is an extension for the Doctrine DBAL and ORM library.
39
 */
40
class DoctrineExtension extends AbstractDoctrineExtension
41
{
42
    /** @var string */
43
    private $defaultConnection;
44
45
    /** @var SymfonyBridgeAdapter */
46
    private $adapter;
47
48
    public function __construct(SymfonyBridgeAdapter $adapter = null)
49
    {
50
        $this->adapter = $adapter ?: new SymfonyBridgeAdapter(new CacheProviderLoader(), 'doctrine.orm', 'orm');
51
    }
52
53
    /**
54
     * {@inheritDoc}
55
     */
56
    public function load(array $configs, ContainerBuilder $container)
57
    {
58
        $configuration = $this->getConfiguration($configs, $container);
59
        $config        = $this->processConfiguration($configuration, $configs);
0 ignored issues
show
Bug introduced by
It seems like $configuration defined by $this->getConfiguration($configs, $container) on line 58 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...
60
61
        $this->adapter->loadServicesConfiguration($container);
62
63
        if (! empty($config['dbal'])) {
64
            $this->dbalLoad($config['dbal'], $container);
65
        }
66
67
        if (empty($config['orm'])) {
68
            return;
69
        }
70
71
        if (empty($config['dbal'])) {
72
            throw new LogicException('Configuring the ORM layer requires to configure the DBAL layer as well.');
73
        }
74
75
        if (! class_exists(Version::class)) {
76
            throw new LogicException('To configure the ORM layer, you must first install the doctrine/orm package.');
77
        }
78
79
        $this->ormLoad($config['orm'], $container);
80
    }
81
82
    /**
83
     * Loads the DBAL configuration.
84
     *
85
     * Usage example:
86
     *
87
     *      <doctrine:dbal id="myconn" dbname="sfweb" user="root" />
88
     *
89
     * @param array            $config    An array of configuration settings
90
     * @param ContainerBuilder $container A ContainerBuilder instance
91
     */
92
    protected function dbalLoad(array $config, ContainerBuilder $container)
93
    {
94
        $loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
95
        $loader->load('dbal.xml');
96
97
        if (empty($config['default_connection'])) {
98
            $keys                         = array_keys($config['connections']);
99
            $config['default_connection'] = reset($keys);
100
        }
101
102
        $this->defaultConnection = $config['default_connection'];
103
104
        $container->setAlias('database_connection', sprintf('doctrine.dbal.%s_connection', $this->defaultConnection));
105
        $container->getAlias('database_connection')->setPublic(true);
106
        $container->setAlias('doctrine.dbal.event_manager', new Alias(sprintf('doctrine.dbal.%s_connection.event_manager', $this->defaultConnection), false));
107
108
        $container->setParameter('doctrine.dbal.connection_factory.types', $config['types']);
109
110
        $connections = [];
111
112
        foreach (array_keys($config['connections']) as $name) {
113
            $connections[$name] = sprintf('doctrine.dbal.%s_connection', $name);
114
        }
115
116
        $container->setParameter('doctrine.connections', $connections);
117
        $container->setParameter('doctrine.default_connection', $this->defaultConnection);
118
119
        foreach ($config['connections'] as $name => $connection) {
120
            $this->loadDbalConnection($name, $connection, $container);
121
        }
122
    }
123
124
    /**
125
     * Loads a configured DBAL connection.
126
     *
127
     * @param string           $name       The name of the connection
128
     * @param array            $connection A dbal connection configuration.
129
     * @param ContainerBuilder $container  A ContainerBuilder instance
130
     */
131
    protected function loadDbalConnection($name, array $connection, ContainerBuilder $container)
132
    {
133
        $configuration = $container->setDefinition(sprintf('doctrine.dbal.%s_connection.configuration', $name), new ChildDefinition('doctrine.dbal.connection.configuration'));
134
        $logger        = null;
135
        if ($connection['logging']) {
136
            $logger = new Reference('doctrine.dbal.logger');
137
        }
138
        unset($connection['logging']);
139
140
        if ($connection['profiling']) {
141
            $profilingAbstractId = $connection['profiling_collect_backtrace'] ?
142
                'doctrine.dbal.logger.backtrace' :
143
                'doctrine.dbal.logger.profiling';
144
145
            $profilingLoggerId = $profilingAbstractId . '.' . $name;
146
            $container->setDefinition($profilingLoggerId, new ChildDefinition($profilingAbstractId));
147
            $profilingLogger = new Reference($profilingLoggerId);
148
            $container->getDefinition('data_collector.doctrine')->addMethodCall('addLogger', [$name, $profilingLogger]);
149
150
            if ($logger !== null) {
151
                $chainLogger = new ChildDefinition('doctrine.dbal.logger.chain');
152
                $chainLogger->addMethodCall('addLogger', [$profilingLogger]);
153
154
                $loggerId = 'doctrine.dbal.logger.chain.' . $name;
155
                $container->setDefinition($loggerId, $chainLogger);
156
                $logger = new Reference($loggerId);
157
            } else {
158
                $logger = $profilingLogger;
159
            }
160
        }
161
        unset($connection['profiling'], $connection['profiling_collect_backtrace']);
162
163
        if (isset($connection['auto_commit'])) {
164
            $configuration->addMethodCall('setAutoCommit', [$connection['auto_commit']]);
165
        }
166
167
        unset($connection['auto_commit']);
168
169
        if (isset($connection['schema_filter']) && $connection['schema_filter']) {
170
            if (method_exists(\Doctrine\DBAL\Configuration::class, 'setSchemaAssetsFilter')) {
171
                $definition = new Definition(RegexSchemaAssetFilter::class, [$connection['schema_filter']]);
172
                $definition->addTag('doctrine.dbal.schema_filter', ['connection' => $name]);
173
                $container->setDefinition(sprintf('doctrine.dbal.%s_regex_schema_filter', $name), $definition);
174
            } else {
175
                // backwards compatibility with dbal < 2.9
176
                $configuration->addMethodCall('setFilterSchemaAssetsExpression', [$connection['schema_filter']]);
177
            }
178
        }
179
180
        unset($connection['schema_filter']);
181
182
        if ($logger) {
183
            $configuration->addMethodCall('setSQLLogger', [$logger]);
184
        }
185
186
        // event manager
187
        $container->setDefinition(sprintf('doctrine.dbal.%s_connection.event_manager', $name), new ChildDefinition('doctrine.dbal.connection.event_manager'));
188
189
        // connection
190
        $options = $this->getConnectionOptions($connection);
191
192
        $def = $container
193
            ->setDefinition(sprintf('doctrine.dbal.%s_connection', $name), new ChildDefinition('doctrine.dbal.connection'))
194
            ->setPublic(true)
195
            ->setArguments([
196
                $options,
197
                new Reference(sprintf('doctrine.dbal.%s_connection.configuration', $name)),
198
                new Reference(sprintf('doctrine.dbal.%s_connection.event_manager', $name)),
199
                $connection['mapping_types'],
200
            ]);
201
202
        // Set class in case "wrapper_class" option was used to assist IDEs
203
        if (isset($options['wrapperClass'])) {
204
            $def->setClass($options['wrapperClass']);
205
        }
206
207
        if (! empty($connection['use_savepoints'])) {
208
            $def->addMethodCall('setNestTransactionsWithSavepoints', [$connection['use_savepoints']]);
209
        }
210
211
        // Create a shard_manager for this connection
212
        if (! isset($options['shards'])) {
213
            return;
214
        }
215
216
        $shardManagerDefinition = new Definition($options['shardManagerClass'], [new Reference(sprintf('doctrine.dbal.%s_connection', $name))]);
217
        $container->setDefinition(sprintf('doctrine.dbal.%s_shard_manager', $name), $shardManagerDefinition);
218
    }
219
220
    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...
221
    {
222
        $options = $connection;
223
224
        if (isset($options['platform_service'])) {
225
            $options['platform'] = new Reference($options['platform_service']);
226
            unset($options['platform_service']);
227
        }
228
        unset($options['mapping_types']);
229
230
        if (isset($options['shard_choser_service'])) {
231
            $options['shard_choser'] = new Reference($options['shard_choser_service']);
232
            unset($options['shard_choser_service']);
233
        }
234
235
        foreach ([
236
            'options' => 'driverOptions',
237
            'driver_class' => 'driverClass',
238
            'wrapper_class' => 'wrapperClass',
239
            'keep_slave' => 'keepSlave',
240
            'shard_choser' => 'shardChoser',
241
            'shard_manager_class' => 'shardManagerClass',
242
            'server_version' => 'serverVersion',
243
            'default_table_options' => 'defaultTableOptions',
244
        ] as $old => $new) {
245
            if (! isset($options[$old])) {
246
                continue;
247
            }
248
249
            $options[$new] = $options[$old];
250
            unset($options[$old]);
251
        }
252
253
        if (! empty($options['slaves']) && ! empty($options['shards'])) {
254
            throw new InvalidArgumentException('Sharding and master-slave connection cannot be used together');
255
        }
256
257
        if (! empty($options['slaves'])) {
258
            $nonRewrittenKeys = [
259
                'driver' => true,
260
                'driverOptions' => true,
261
                'driverClass' => true,
262
                'wrapperClass' => true,
263
                'keepSlave' => true,
264
                'shardChoser' => true,
265
                'platform' => true,
266
                'slaves' => true,
267
                'master' => true,
268
                'shards' => true,
269
                'serverVersion' => true,
270
                'defaultTableOptions' => true,
271
                // included by safety but should have been unset already
272
                'logging' => true,
273
                'profiling' => true,
274
                'mapping_types' => true,
275
                'platform_service' => true,
276
            ];
277 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...
278
                if (isset($nonRewrittenKeys[$key])) {
279
                    continue;
280
                }
281
                $options['master'][$key] = $value;
282
                unset($options[$key]);
283
            }
284
            if (empty($options['wrapperClass'])) {
285
                // Change the wrapper class only if the user does not already forced using a custom one.
286
                $options['wrapperClass'] = 'Doctrine\\DBAL\\Connections\\MasterSlaveConnection';
287
            }
288
        } else {
289
            unset($options['slaves']);
290
        }
291
292
        if (! empty($options['shards'])) {
293
            $nonRewrittenKeys = [
294
                'driver' => true,
295
                'driverOptions' => true,
296
                'driverClass' => true,
297
                'wrapperClass' => true,
298
                'keepSlave' => true,
299
                'shardChoser' => true,
300
                'platform' => true,
301
                'slaves' => true,
302
                'global' => true,
303
                'shards' => true,
304
                'serverVersion' => true,
305
                'defaultTableOptions' => true,
306
                // included by safety but should have been unset already
307
                'logging' => true,
308
                'profiling' => true,
309
                'mapping_types' => true,
310
                'platform_service' => true,
311
            ];
312 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...
313
                if (isset($nonRewrittenKeys[$key])) {
314
                    continue;
315
                }
316
                $options['global'][$key] = $value;
317
                unset($options[$key]);
318
            }
319
            if (empty($options['wrapperClass'])) {
320
                // Change the wrapper class only if the user does not already forced using a custom one.
321
                $options['wrapperClass'] = 'Doctrine\\DBAL\\Sharding\\PoolingShardConnection';
322
            }
323
            if (empty($options['shardManagerClass'])) {
324
                // Change the shard manager class only if the user does not already forced using a custom one.
325
                $options['shardManagerClass'] = 'Doctrine\\DBAL\\Sharding\\PoolingShardManager';
326
            }
327
        } else {
328
            unset($options['shards']);
329
        }
330
331
        return $options;
332
    }
333
334
    /**
335
     * Loads the Doctrine ORM configuration.
336
     *
337
     * Usage example:
338
     *
339
     *     <doctrine:orm id="mydm" connection="myconn" />
340
     *
341
     * @param array            $config    An array of configuration settings
342
     * @param ContainerBuilder $container A ContainerBuilder instance
343
     */
344
    protected function ormLoad(array $config, ContainerBuilder $container)
345
    {
346
        $loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
347
        $loader->load('orm.xml');
348
349
        if (class_exists(AbstractType::class)) {
350
            $container->getDefinition('form.type.entity')->addTag('kernel.reset', ['method' => 'reset']);
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', 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
        $loadPropertyInfoExtractor = interface_exists(PropertyInfoExtractorInterface::class)
376
            && class_exists(DoctrineExtractor::class);
377
378
        foreach ($config['entity_managers'] as $name => $entityManager) {
379
            $entityManager['name'] = $name;
380
            $this->loadOrmEntityManager($entityManager, $container);
381
382
            if ($loadPropertyInfoExtractor) {
383
                $this->loadPropertyInfoExtractor($name, $container);
384
            }
385
386
            $this->loadValidatorLoader($name, $container);
387
        }
388
389
        if ($config['resolve_target_entities']) {
390
            $def = $container->findDefinition('doctrine.orm.listeners.resolve_target_entity');
391
            foreach ($config['resolve_target_entities'] as $name => $implementation) {
392
                $def->addMethodCall('addResolveTargetEntity', [
393
                    $name,
394
                    $implementation,
395
                    [],
396
                ]);
397
            }
398
399
            $def->addTag('doctrine.event_subscriber');
400
        }
401
402
        $container->registerForAutoconfiguration(ServiceEntityRepositoryInterface::class)
403
            ->addTag(ServiceRepositoryCompilerPass::REPOSITORY_SERVICE_TAG);
404
405
        $this->loadMessengerServices($container);
406
    }
407
408
    /**
409
     * Loads a configured ORM entity manager.
410
     *
411
     * @param array            $entityManager A configured ORM entity manager.
412
     * @param ContainerBuilder $container     A ContainerBuilder instance
413
     */
414
    protected function loadOrmEntityManager(array $entityManager, ContainerBuilder $container)
415
    {
416
        $ormConfigDef = $container->setDefinition(sprintf('doctrine.orm.%s_configuration', $entityManager['name']), new ChildDefinition('doctrine.orm.configuration'));
417
418
        $this->loadOrmEntityManagerMappingInformation($entityManager, $ormConfigDef, $container);
419
        $this->loadOrmCacheDrivers($entityManager, $container);
420
421
        if (isset($entityManager['entity_listener_resolver']) && $entityManager['entity_listener_resolver']) {
422
            $container->setAlias(sprintf('doctrine.orm.%s_entity_listener_resolver', $entityManager['name']), $entityManager['entity_listener_resolver']);
423
        } else {
424
            $definition = new Definition('%doctrine.orm.entity_listener_resolver.class%');
425
            $definition->addArgument(new Reference('service_container'));
426
            $container->setDefinition(sprintf('doctrine.orm.%s_entity_listener_resolver', $entityManager['name']), $definition);
427
        }
428
429
        $methods = [
430
            'setMetadataCacheImpl' => new Reference(sprintf('doctrine.orm.%s_metadata_cache', $entityManager['name'])),
431
            'setQueryCacheImpl' => new Reference(sprintf('doctrine.orm.%s_query_cache', $entityManager['name'])),
432
            'setResultCacheImpl' => new Reference(sprintf('doctrine.orm.%s_result_cache', $entityManager['name'])),
433
            'setMetadataDriverImpl' => new Reference('doctrine.orm.' . $entityManager['name'] . '_metadata_driver'),
434
            'setProxyDir' => '%doctrine.orm.proxy_dir%',
435
            'setProxyNamespace' => '%doctrine.orm.proxy_namespace%',
436
            'setAutoGenerateProxyClasses' => '%doctrine.orm.auto_generate_proxy_classes%',
437
            'setClassMetadataFactoryName' => $entityManager['class_metadata_factory_name'],
438
            'setDefaultRepositoryClassName' => $entityManager['default_repository_class'],
439
            'setNamingStrategy' => new Reference($entityManager['naming_strategy']),
440
            'setQuoteStrategy' => new Reference($entityManager['quote_strategy']),
441
            'setEntityListenerResolver' => new Reference(sprintf('doctrine.orm.%s_entity_listener_resolver', $entityManager['name'])),
442
        ];
443
444
        $listenerId        = sprintf('doctrine.orm.%s_listeners.attach_entity_listeners', $entityManager['name']);
445
        $listenerDef       = $container->setDefinition($listenerId, new Definition('%doctrine.orm.listeners.attach_entity_listeners.class%'));
446
        $listenerTagParams = ['event' => 'loadClassMetadata'];
447
        if (isset($entityManager['connection'])) {
448
            $listenerTagParams['connection'] = $entityManager['connection'];
449
        }
450
        $listenerDef->addTag('doctrine.event_listener', $listenerTagParams);
451
452
        if (isset($entityManager['second_level_cache'])) {
453
            $this->loadOrmSecondLevelCache($entityManager, $ormConfigDef, $container);
454
        }
455
456
        if ($entityManager['repository_factory']) {
457
            $methods['setRepositoryFactory'] = new Reference($entityManager['repository_factory']);
458
        }
459
460
        foreach ($methods as $method => $arg) {
461
            $ormConfigDef->addMethodCall($method, [$arg]);
462
        }
463
464
        foreach ($entityManager['hydrators'] as $name => $class) {
465
            $ormConfigDef->addMethodCall('addCustomHydrationMode', [$name, $class]);
466
        }
467
468
        if (! empty($entityManager['dql'])) {
469
            foreach ($entityManager['dql']['string_functions'] as $name => $function) {
470
                $ormConfigDef->addMethodCall('addCustomStringFunction', [$name, $function]);
471
            }
472
            foreach ($entityManager['dql']['numeric_functions'] as $name => $function) {
473
                $ormConfigDef->addMethodCall('addCustomNumericFunction', [$name, $function]);
474
            }
475
            foreach ($entityManager['dql']['datetime_functions'] as $name => $function) {
476
                $ormConfigDef->addMethodCall('addCustomDatetimeFunction', [$name, $function]);
477
            }
478
        }
479
480
        $enabledFilters    = [];
481
        $filtersParameters = [];
482
        foreach ($entityManager['filters'] as $name => $filter) {
483
            $ormConfigDef->addMethodCall('addFilter', [$name, $filter['class']]);
484
            if ($filter['enabled']) {
485
                $enabledFilters[] = $name;
486
            }
487
            if (! $filter['parameters']) {
488
                continue;
489
            }
490
491
            $filtersParameters[$name] = $filter['parameters'];
492
        }
493
494
        $managerConfiguratorName = sprintf('doctrine.orm.%s_manager_configurator', $entityManager['name']);
495
        $container
496
            ->setDefinition($managerConfiguratorName, new ChildDefinition('doctrine.orm.manager_configurator.abstract'))
497
            ->replaceArgument(0, $enabledFilters)
498
            ->replaceArgument(1, $filtersParameters);
499
500
        if (! isset($entityManager['connection'])) {
501
            $entityManager['connection'] = $this->defaultConnection;
502
        }
503
504
        $container
505
            ->setDefinition(sprintf('doctrine.orm.%s_entity_manager', $entityManager['name']), new ChildDefinition('doctrine.orm.entity_manager.abstract'))
506
            ->setPublic(true)
507
            ->setArguments([
508
                new Reference(sprintf('doctrine.dbal.%s_connection', $entityManager['connection'])),
509
                new Reference(sprintf('doctrine.orm.%s_configuration', $entityManager['name'])),
510
            ])
511
            ->setConfigurator([new Reference($managerConfiguratorName), 'configure']);
512
513
        $container->setAlias(
514
            sprintf('doctrine.orm.%s_entity_manager.event_manager', $entityManager['name']),
515
            new Alias(sprintf('doctrine.dbal.%s_connection.event_manager', $entityManager['connection']), false)
516
        );
517
518
        if (! isset($entityManager['entity_listeners'])) {
519
            return;
520
        }
521
522
        if (! isset($listenerDef)) {
523
            throw new InvalidArgumentException('Entity listeners configuration requires doctrine-orm 2.5.0 or newer');
524
        }
525
526
        $entities = $entityManager['entity_listeners']['entities'];
527
528
        foreach ($entities as $entityListenerClass => $entity) {
529
            foreach ($entity['listeners'] as $listenerClass => $listener) {
530
                foreach ($listener['events'] as $listenerEvent) {
531
                    $listenerEventName = $listenerEvent['type'];
532
                    $listenerMethod    = $listenerEvent['method'];
533
534
                    $listenerDef->addMethodCall('addEntityListener', [
535
                        $entityListenerClass,
536
                        $listenerClass,
537
                        $listenerEventName,
538
                        $listenerMethod,
539
                    ]);
540
                }
541
            }
542
        }
543
    }
544
545
    /**
546
     * Loads an ORM entity managers bundle mapping information.
547
     *
548
     * There are two distinct configuration possibilities for mapping information:
549
     *
550
     * 1. Specify a bundle and optionally details where the entity and mapping information reside.
551
     * 2. Specify an arbitrary mapping location.
552
     *
553
     * @param array            $entityManager A configured ORM entity manager
554
     * @param Definition       $ormConfigDef  A Definition instance
555
     * @param ContainerBuilder $container     A ContainerBuilder instance
556
     *
557
     * @example
558
     *
559
     *  doctrine.orm:
560
     *     mappings:
561
     *         MyBundle1: ~
562
     *         MyBundle2: yml
563
     *         MyBundle3: { type: annotation, dir: Entities/ }
564
     *         MyBundle4: { type: xml, dir: Resources/config/doctrine/mapping }
565
     *         MyBundle5:
566
     *             type: yml
567
     *             dir: bundle-mappings/
568
     *             alias: BundleAlias
569
     *         arbitrary_key:
570
     *             type: xml
571
     *             dir: %kernel.root_dir%/../src/vendor/DoctrineExtensions/lib/DoctrineExtensions/Entities
572
     *             prefix: DoctrineExtensions\Entities\
573
     *             alias: DExt
574
     *
575
     * In the case of bundles everything is really optional (which leads to autodetection for this bundle) but
576
     * in the mappings key everything except alias is a required argument.
577
     */
578
    protected function loadOrmEntityManagerMappingInformation(array $entityManager, Definition $ormConfigDef, ContainerBuilder $container)
579
    {
580
        // reset state of drivers and alias map. They are only used by this methods and children.
581
        $this->drivers  = [];
582
        $this->aliasMap = [];
583
584
        $this->loadMappingInformation($entityManager, $container);
585
        $this->registerMappingDrivers($entityManager, $container);
586
587
        $ormConfigDef->addMethodCall('setEntityNamespaces', [$this->aliasMap]);
588
    }
589
590
    /**
591
     * Loads an ORM second level cache bundle mapping information.
592
     *
593
     * @param array            $entityManager A configured ORM entity manager
594
     * @param Definition       $ormConfigDef  A Definition instance
595
     * @param ContainerBuilder $container     A ContainerBuilder instance
596
     *
597
     * @example
598
     *  entity_managers:
599
     *      default:
600
     *          second_level_cache:
601
     *              region_cache_driver: apc
602
     *              log_enabled: true
603
     *              regions:
604
     *                  my_service_region:
605
     *                      type: service
606
     *                      service : "my_service_region"
607
     *
608
     *                  my_query_region:
609
     *                      lifetime: 300
610
     *                      cache_driver: array
611
     *                      type: filelock
612
     *
613
     *                  my_entity_region:
614
     *                      lifetime: 600
615
     *                      cache_driver:
616
     *                          type: apc
617
     */
618
    protected function loadOrmSecondLevelCache(array $entityManager, Definition $ormConfigDef, ContainerBuilder $container)
619
    {
620
        $driverId = null;
621
        $enabled  = $entityManager['second_level_cache']['enabled'];
622
623
        if (isset($entityManager['second_level_cache']['region_cache_driver'])) {
624
            $driverName = 'second_level_cache.region_cache_driver';
625
            $driverMap  = $entityManager['second_level_cache']['region_cache_driver'];
626
            $driverId   = $this->loadCacheDriver($driverName, $entityManager['name'], $driverMap, $container);
627
        }
628
629
        $configId   = sprintf('doctrine.orm.%s_second_level_cache.cache_configuration', $entityManager['name']);
630
        $regionsId  = sprintf('doctrine.orm.%s_second_level_cache.regions_configuration', $entityManager['name']);
631
        $driverId   = $driverId ?: sprintf('doctrine.orm.%s_second_level_cache.region_cache_driver', $entityManager['name']);
632
        $configDef  = $container->setDefinition($configId, new Definition('%doctrine.orm.second_level_cache.cache_configuration.class%'));
633
        $regionsDef = $container->setDefinition($regionsId, new Definition('%doctrine.orm.second_level_cache.regions_configuration.class%'));
634
635
        $slcFactoryId = sprintf('doctrine.orm.%s_second_level_cache.default_cache_factory', $entityManager['name']);
636
        $factoryClass = isset($entityManager['second_level_cache']['factory']) ? $entityManager['second_level_cache']['factory'] : '%doctrine.orm.second_level_cache.default_cache_factory.class%';
637
638
        $definition = new Definition($factoryClass, [new Reference($regionsId), new Reference($driverId)]);
639
640
        $slcFactoryDef = $container
641
            ->setDefinition($slcFactoryId, $definition);
642
643
        if (isset($entityManager['second_level_cache']['regions'])) {
644
            foreach ($entityManager['second_level_cache']['regions'] as $name => $region) {
645
                $regionRef  = null;
646
                $regionType = $region['type'];
647
648
                if ($regionType === 'service') {
649
                    $regionId  = sprintf('doctrine.orm.%s_second_level_cache.region.%s', $entityManager['name'], $name);
650
                    $regionRef = new Reference($region['service']);
651
652
                    $container->setAlias($regionId, new Alias($region['service'], false));
653
                }
654
655
                if ($regionType === 'default' || $regionType === 'filelock') {
656
                    $regionId   = sprintf('doctrine.orm.%s_second_level_cache.region.%s', $entityManager['name'], $name);
657
                    $driverName = sprintf('second_level_cache.region.%s_driver', $name);
658
                    $driverMap  = $region['cache_driver'];
659
                    $driverId   = $this->loadCacheDriver($driverName, $entityManager['name'], $driverMap, $container);
660
                    $regionRef  = new Reference($regionId);
661
662
                    $container
663
                        ->setDefinition($regionId, new Definition('%doctrine.orm.second_level_cache.default_region.class%'))
664
                        ->setArguments([$name, new Reference($driverId), $region['lifetime']]);
665
                }
666
667
                if ($regionType === 'filelock') {
668
                    $regionId = sprintf('doctrine.orm.%s_second_level_cache.region.%s_filelock', $entityManager['name'], $name);
669
670
                    $container
671
                        ->setDefinition($regionId, new Definition('%doctrine.orm.second_level_cache.filelock_region.class%'))
672
                        ->setArguments([$regionRef, $region['lock_path'], $region['lock_lifetime']]);
673
674
                    $regionRef = new Reference($regionId);
675
                    $regionsDef->addMethodCall('getLockLifetime', [$name, $region['lock_lifetime']]);
676
                }
677
678
                $regionsDef->addMethodCall('setLifetime', [$name, $region['lifetime']]);
679
                $slcFactoryDef->addMethodCall('setRegion', [$regionRef]);
680
            }
681
        }
682
683
        if ($entityManager['second_level_cache']['log_enabled']) {
684
            $loggerChainId   = sprintf('doctrine.orm.%s_second_level_cache.logger_chain', $entityManager['name']);
685
            $loggerStatsId   = sprintf('doctrine.orm.%s_second_level_cache.logger_statistics', $entityManager['name']);
686
            $loggerChaingDef = $container->setDefinition($loggerChainId, new Definition('%doctrine.orm.second_level_cache.logger_chain.class%'));
687
            $loggerStatsDef  = $container->setDefinition($loggerStatsId, new Definition('%doctrine.orm.second_level_cache.logger_statistics.class%'));
688
689
            $loggerChaingDef->addMethodCall('setLogger', ['statistics', $loggerStatsDef]);
690
            $configDef->addMethodCall('setCacheLogger', [$loggerChaingDef]);
691
692
            foreach ($entityManager['second_level_cache']['loggers'] as $name => $logger) {
693
                $loggerId  = sprintf('doctrine.orm.%s_second_level_cache.logger.%s', $entityManager['name'], $name);
694
                $loggerRef = new Reference($logger['service']);
695
696
                $container->setAlias($loggerId, new Alias($logger['service'], false));
697
                $loggerChaingDef->addMethodCall('setLogger', [$name, $loggerRef]);
698
            }
699
        }
700
701
        $configDef->addMethodCall('setCacheFactory', [$slcFactoryDef]);
702
        $configDef->addMethodCall('setRegionsConfiguration', [$regionsDef]);
703
        $ormConfigDef->addMethodCall('setSecondLevelCacheEnabled', [$enabled]);
704
        $ormConfigDef->addMethodCall('setSecondLevelCacheConfiguration', [$configDef]);
705
    }
706
707
    /**
708
     * {@inheritDoc}
709
     */
710
    protected function getObjectManagerElementName($name)
711
    {
712
        return 'doctrine.orm.' . $name;
713
    }
714
715
    protected function getMappingObjectDefaultName()
716
    {
717
        return 'Entity';
718
    }
719
720
    /**
721
     * {@inheritDoc}
722
     */
723
    protected function getMappingResourceConfigDirectory()
724
    {
725
        return 'Resources/config/doctrine';
726
    }
727
728
    /**
729
     * {@inheritDoc}
730
     */
731
    protected function getMappingResourceExtension()
732
    {
733
        return 'orm';
734
    }
735
736
    /**
737
     * {@inheritDoc}
738
     */
739
    protected function loadCacheDriver($driverName, $entityManagerName, array $driverMap, ContainerBuilder $container)
740
    {
741
        $serviceId = null;
742
        $aliasId   = $this->getObjectManagerElementName(sprintf('%s_%s', $entityManagerName, $driverName));
743
744
        if ($driverMap['type'] === null) {
745
            $driverMap = [
746
                'type' => 'pool',
747
                'pool' => $this->getPoolNameForCacheDriver($driverName),
748
            ];
749
        }
750
751
        $usesDeprecatedCacheType = true;
752
753
        switch ($driverMap['type']) {
754
            case 'service':
755
                $usesDeprecatedCacheType = false;
756
                $serviceId               = $driverMap['id'];
757
                break;
758
759
            case 'pool':
760
                $usesDeprecatedCacheType = false;
761
                $serviceId               = $this->createPoolCacheDefinition($container, $driverMap['pool']);
762
                break;
763
764
            case 'provider':
765
                $serviceId = sprintf('doctrine_cache.providers.%s', $driverMap['cache_provider']);
766
                break;
767
        }
768
769
        if ($usesDeprecatedCacheType) {
770
            @trigger_error(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
771
                sprintf(
772
                    'Using the "%s" type for cache "%s" is deprecated since DoctrineBundle 1.12 and will be dropped in 2.0. Please use the "id" or "pool" types exclusively.',
773
                    $driverMap['type'],
774
                    $driverName
775
                ),
776
                E_USER_DEPRECATED
777
            );
778
        }
779
780
        if ($serviceId !== null) {
781
            $container->setAlias($aliasId, new Alias($serviceId, false));
782
783
            return $aliasId;
784
        }
785
786
        return $this->adapter->loadCacheDriver($driverName, $entityManagerName, $driverMap, $container);
787
    }
788
789
    /**
790
     * Loads a configured entity managers cache drivers.
791
     *
792
     * @param array            $entityManager A configured ORM entity manager.
793
     * @param ContainerBuilder $container     A ContainerBuilder instance
794
     */
795
    protected function loadOrmCacheDrivers(array $entityManager, ContainerBuilder $container)
796
    {
797
        $this->loadCacheDriver('metadata_cache', $entityManager['name'], $entityManager['metadata_cache_driver'], $container);
798
        $this->loadCacheDriver('result_cache', $entityManager['name'], $entityManager['result_cache_driver'], $container);
799
        $this->loadCacheDriver('query_cache', $entityManager['name'], $entityManager['query_cache_driver'], $container);
800
    }
801
802
    /**
803
     * Loads a property info extractor for each defined entity manager.
804
     *
805
     * @param string $entityManagerName
806
     */
807
    private function loadPropertyInfoExtractor($entityManagerName, ContainerBuilder $container)
808
    {
809
        $propertyExtractorDefinition = $container->register(sprintf('doctrine.orm.%s_entity_manager.property_info_extractor', $entityManagerName), DoctrineExtractor::class);
810
        if (property_exists(DoctrineExtractor::class, 'entityManager')) {
811
            $argumentId = sprintf('doctrine.orm.%s_entity_manager', $entityManagerName);
812
        } else {
813
            $argumentId = sprintf('doctrine.orm.%s_entity_manager.metadata_factory', $entityManagerName);
814
815
            $metadataFactoryDefinition = $container->register($argumentId, ClassMetadataFactory::class);
816
            $metadataFactoryDefinition->setFactory([
817
                new Reference(sprintf('doctrine.orm.%s_entity_manager', $entityManagerName)),
818
                'getMetadataFactory',
819
            ]);
820
            $metadataFactoryDefinition->setPublic(false);
821
        }
822
823
        $propertyExtractorDefinition->addArgument(new Reference($argumentId));
824
825
        $propertyExtractorDefinition->addTag('property_info.list_extractor', ['priority' => -1001]);
826
        $propertyExtractorDefinition->addTag('property_info.type_extractor', ['priority' => -999]);
827
828
        if (! is_a(DoctrineExtractor::class, PropertyAccessExtractorInterface::class, true)) {
829
            return;
830
        }
831
832
        $propertyExtractorDefinition->addTag('property_info.access_extractor', ['priority' => -999]);
833
    }
834
835
    /**
836
     * Loads a validator loader for each defined entity manager.
837
     */
838
    private function loadValidatorLoader(string $entityManagerName, ContainerBuilder $container) : void
839
    {
840
        if (! interface_exists(LoaderInterface::class) || ! class_exists(DoctrineLoader::class)) {
841
            return;
842
        }
843
844
        $validatorLoaderDefinition = $container->register(sprintf('doctrine.orm.%s_entity_manager.validator_loader', $entityManagerName), DoctrineLoader::class);
845
        $validatorLoaderDefinition->addArgument(new Reference(sprintf('doctrine.orm.%s_entity_manager', $entityManagerName)));
846
847
        $validatorLoaderDefinition->addTag('validator.auto_mapper', ['priority' => -100]);
848
    }
849
850
    /**
851
     * @param array  $objectManager
852
     * @param string $cacheName
853
     */
854
    public function loadObjectManagerCacheDriver(array $objectManager, ContainerBuilder $container, $cacheName)
855
    {
856
        $this->loadCacheDriver($cacheName, $objectManager['name'], $objectManager[$cacheName . '_driver'], $container);
857
    }
858
859
    /**
860
     * {@inheritDoc}
861
     */
862
    public function getXsdValidationBasePath()
863
    {
864
        return __DIR__ . '/../Resources/config/schema';
0 ignored issues
show
Bug Best Practice introduced by
The return type of return __DIR__ . '/../Resources/config/schema'; (string) is incompatible with the return type of the parent method Symfony\Component\Depend...etXsdValidationBasePath of type boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
865
    }
866
867
    /**
868
     * {@inheritDoc}
869
     */
870
    public function getNamespace()
871
    {
872
        return 'http://symfony.com/schema/dic/doctrine';
873
    }
874
875
    /**
876
     * {@inheritDoc}
877
     */
878
    public function getConfiguration(array $config, ContainerBuilder $container)
879
    {
880
        return new Configuration($container->getParameter('kernel.debug'));
881
    }
882
883
    private function loadMessengerServices(ContainerBuilder $container) : void
884
    {
885
        // If the Messenger component is installed and the doctrine transaction middleware is available, wire it:
886
        if (! interface_exists(MessageBusInterface::class) || ! class_exists(DoctrineTransactionMiddleware::class)) {
887
            return;
888
        }
889
890
        $loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
891
        $loader->load('messenger.xml');
892
893
        if (! class_exists(DoctrineTransportFactory::class)) {
894
            return;
895
        }
896
897
        $transportFactoryDefinition = $container->getDefinition('messenger.transport.doctrine.factory');
898
        $transportFactoryDefinition->addTag('messenger.transport_factory');
899
    }
900
901
    private function createPoolCacheDefinition(ContainerBuilder $container, string $poolName) : string
902
    {
903
        if (! class_exists(DoctrineProvider::class)) {
904
            throw new LogicException('Using the "pool" cache type is only supported when symfony/cache is installed.');
905
        }
906
907
        $serviceId = sprintf('doctrine.orm.cache.pool.%s', $poolName);
908
909
        $definition = $container->register($serviceId, DoctrineProvider::class);
910
        $definition->addArgument(new Reference($poolName));
911
        $definition->setPrivate(true);
912
913
        return $serviceId;
914
    }
915
916
    private function getPoolNameForCacheDriver(string $driverName) : string
917
    {
918
        switch ($driverName) {
919
            case 'metadata_cache':
920
                return 'cache.system';
921
            default:
922
                return 'cache.app';
923
        }
924
    }
925
}
926