Completed
Pull Request — master (#831)
by Kévin
08:09
created

loadOrmEntityManagerMappingInformation()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 11
rs 9.9
cc 1
nc 1
nop 3
1
<?php
2
3
namespace Doctrine\Bundle\DoctrineBundle\DependencyInjection;
4
5
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\ServiceRepositoryCompilerPass;
6
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepositoryInterface;
7
use Doctrine\Bundle\DoctrineCacheBundle\DependencyInjection\CacheProviderLoader;
8
use Doctrine\Bundle\DoctrineCacheBundle\DependencyInjection\SymfonyBridgeAdapter;
9
use Doctrine\Common\Persistence\Mapping\ClassMetadataFactory;
10
use Doctrine\ORM\Version;
11
use Symfony\Bridge\Doctrine\DependencyInjection\AbstractDoctrineExtension;
12
use Symfony\Bridge\Doctrine\Form\Type\DoctrineType;
13
use Symfony\Bridge\Doctrine\Validator\DoctrineLoader;
14
use Symfony\Component\Config\FileLocator;
15
use Symfony\Component\DependencyInjection\Alias;
16
use Symfony\Component\DependencyInjection\ChildDefinition;
17
use Symfony\Component\DependencyInjection\ContainerBuilder;
18
use Symfony\Component\DependencyInjection\Definition;
19
use Symfony\Component\DependencyInjection\DefinitionDecorator;
20
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
21
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
22
use Symfony\Component\DependencyInjection\Reference;
23
use Symfony\Component\DependencyInjection\ServiceLocator;
24
use Symfony\Component\Form\AbstractType;
25
26
/**
27
 * DoctrineExtension is an extension for the Doctrine DBAL and ORM library.
28
 */
29
class DoctrineExtension extends AbstractDoctrineExtension
30
{
31
    /** @var string */
32
    private $defaultConnection;
33
34
    /** @var SymfonyBridgeAdapter */
35
    private $adapter;
36
37
    public function __construct(SymfonyBridgeAdapter $adapter = null)
38
    {
39
        $this->adapter = $adapter ?: new SymfonyBridgeAdapter(new CacheProviderLoader(), 'doctrine.orm', 'orm');
40
    }
41
42
    /**
43
     * {@inheritDoc}
44
     */
45
    public function load(array $configs, ContainerBuilder $container)
46
    {
47
        $configuration = $this->getConfiguration($configs, $container);
48
        $config        = $this->processConfiguration($configuration, $configs);
0 ignored issues
show
Bug introduced by
It seems like $configuration defined by $this->getConfiguration($configs, $container) on line 47 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...
49
50
        $this->adapter->loadServicesConfiguration($container);
51
52
        if (! empty($config['dbal'])) {
53
            $this->dbalLoad($config['dbal'], $container);
54
        }
55
56
        if (empty($config['orm'])) {
57
            return;
58
        }
59
60
        if (empty($config['dbal'])) {
61
            throw new \LogicException('Configuring the ORM layer requires to configure the DBAL layer as well.');
62
        }
63
64
        if (! class_exists('Doctrine\ORM\Version')) {
65
            throw new \LogicException('To configure the ORM layer, you must first install the doctrine/orm package.');
66
        }
67
68
        $this->ormLoad($config['orm'], $container);
69
    }
70
71
    /**
72
     * Loads the DBAL configuration.
73
     *
74
     * Usage example:
75
     *
76
     *      <doctrine:dbal id="myconn" dbname="sfweb" user="root" />
77
     *
78
     * @param array            $config    An array of configuration settings
79
     * @param ContainerBuilder $container A ContainerBuilder instance
80
     */
81
    protected function dbalLoad(array $config, ContainerBuilder $container)
82
    {
83
        $loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
84
        $loader->load('dbal.xml');
85
86
        if (empty($config['default_connection'])) {
87
            $keys                         = array_keys($config['connections']);
88
            $config['default_connection'] = reset($keys);
89
        }
90
91
        $this->defaultConnection = $config['default_connection'];
92
93
        $container->setAlias('database_connection', sprintf('doctrine.dbal.%s_connection', $this->defaultConnection));
94
        $container->getAlias('database_connection')->setPublic(true);
95
        $container->setAlias('doctrine.dbal.event_manager', new Alias(sprintf('doctrine.dbal.%s_connection.event_manager', $this->defaultConnection), false));
96
97
        $container->setParameter('doctrine.dbal.connection_factory.types', $config['types']);
98
99
        $connections = [];
100
101
        foreach (array_keys($config['connections']) as $name) {
102
            $connections[$name] = sprintf('doctrine.dbal.%s_connection', $name);
103
        }
104
105
        $container->setParameter('doctrine.connections', $connections);
106
        $container->setParameter('doctrine.default_connection', $this->defaultConnection);
107
108
        foreach ($config['connections'] as $name => $connection) {
109
            $this->loadDbalConnection($name, $connection, $container);
110
        }
111
    }
112
113
    /**
114
     * Loads a configured DBAL connection.
115
     *
116
     * @param string           $name       The name of the connection
117
     * @param array            $connection A dbal connection configuration.
118
     * @param ContainerBuilder $container  A ContainerBuilder instance
119
     */
120
    protected function loadDbalConnection($name, array $connection, ContainerBuilder $container)
121
    {
122
        // configuration
123
        $definitionClassname = $this->getDefinitionClassname();
124
125
        $configuration = $container->setDefinition(sprintf('doctrine.dbal.%s_connection.configuration', $name), new $definitionClassname('doctrine.dbal.connection.configuration'));
126
        $logger        = null;
127
        if ($connection['logging']) {
128
            $logger = new Reference('doctrine.dbal.logger');
129
        }
130
        unset($connection['logging']);
131
        if ($connection['profiling']) {
132
            $profilingLoggerId = 'doctrine.dbal.logger.profiling.' . $name;
133
            $container->setDefinition($profilingLoggerId, new $definitionClassname('doctrine.dbal.logger.profiling'));
134
            $profilingLogger = new Reference($profilingLoggerId);
135
            $container->getDefinition('data_collector.doctrine')->addMethodCall('addLogger', [$name, $profilingLogger]);
136
137
            if ($logger !== null) {
138
                $chainLogger = new $definitionClassname('doctrine.dbal.logger.chain');
139
                $chainLogger->addMethodCall('addLogger', [$profilingLogger]);
140
141
                $loggerId = 'doctrine.dbal.logger.chain.' . $name;
142
                $container->setDefinition($loggerId, $chainLogger);
143
                $logger = new Reference($loggerId);
144
            } else {
145
                $logger = $profilingLogger;
146
            }
147
        }
148
        unset($connection['profiling']);
149
150
        if (isset($connection['auto_commit'])) {
151
            $configuration->addMethodCall('setAutoCommit', [$connection['auto_commit']]);
152
        }
153
154
        unset($connection['auto_commit']);
155
156
        if (isset($connection['schema_filter']) && $connection['schema_filter']) {
157
            $configuration->addMethodCall('setFilterSchemaAssetsExpression', [$connection['schema_filter']]);
158
        }
159
160
        unset($connection['schema_filter']);
161
162
        if ($logger) {
163
            $configuration->addMethodCall('setSQLLogger', [$logger]);
164
        }
165
166
        // event manager
167
        $container->setDefinition(sprintf('doctrine.dbal.%s_connection.event_manager', $name), new $definitionClassname('doctrine.dbal.connection.event_manager'));
168
169
        // connection
170
        $options = $this->getConnectionOptions($connection);
171
172
        $def = $container
173
            ->setDefinition(sprintf('doctrine.dbal.%s_connection', $name), new $definitionClassname('doctrine.dbal.connection'))
174
            ->setPublic(true)
175
            ->setArguments([
176
                $options,
177
                new Reference(sprintf('doctrine.dbal.%s_connection.configuration', $name)),
178
                new Reference(sprintf('doctrine.dbal.%s_connection.event_manager', $name)),
179
                $connection['mapping_types'],
180
            ])
181
        ;
182
183
        // Set class in case "wrapper_class" option was used to assist IDEs
184
        if (isset($options['wrapperClass'])) {
185
            $def->setClass($options['wrapperClass']);
186
        }
187
188
        if (! empty($connection['use_savepoints'])) {
189
            $def->addMethodCall('setNestTransactionsWithSavepoints', [$connection['use_savepoints']]);
190
        }
191
192
        // Create a shard_manager for this connection
193
        if (! isset($options['shards'])) {
194
            return;
195
        }
196
197
        $shardManagerDefinition = new Definition($options['shardManagerClass'], [new Reference(sprintf('doctrine.dbal.%s_connection', $name))]);
198
        $container->setDefinition(sprintf('doctrine.dbal.%s_shard_manager', $name), $shardManagerDefinition);
199
    }
200
201
    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...
202
    {
203
        $options = $connection;
204
205
        if (isset($options['platform_service'])) {
206
            $options['platform'] = new Reference($options['platform_service']);
207
            unset($options['platform_service']);
208
        }
209
        unset($options['mapping_types']);
210
211
        if (isset($options['shard_choser_service'])) {
212
            $options['shard_choser'] = new Reference($options['shard_choser_service']);
213
            unset($options['shard_choser_service']);
214
        }
215
216
        foreach ([
217
            'options' => 'driverOptions',
218
            'driver_class' => 'driverClass',
219
            'wrapper_class' => 'wrapperClass',
220
            'keep_slave' => 'keepSlave',
221
            'shard_choser' => 'shardChoser',
222
            'shard_manager_class' => 'shardManagerClass',
223
            'server_version' => 'serverVersion',
224
            'default_table_options' => 'defaultTableOptions',
225
        ] as $old => $new) {
226
            if (! isset($options[$old])) {
227
                continue;
228
            }
229
230
            $options[$new] = $options[$old];
231
            unset($options[$old]);
232
        }
233
234
        if (! empty($options['slaves']) && ! empty($options['shards'])) {
235
            throw new InvalidArgumentException('Sharding and master-slave connection cannot be used together');
236
        }
237
238
        if (! empty($options['slaves'])) {
239
            $nonRewrittenKeys = [
240
                'driver' => true,
241
                'driverOptions' => true,
242
                'driverClass' => true,
243
                'wrapperClass' => true,
244
                'keepSlave' => true,
245
                'shardChoser' => true,
246
                'platform' => true,
247
                'slaves' => true,
248
                'master' => true,
249
                'shards' => true,
250
                'serverVersion' => true,
251
                // included by safety but should have been unset already
252
                'logging' => true,
253
                'profiling' => true,
254
                'mapping_types' => true,
255
                'platform_service' => true,
256
            ];
257 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...
258
                if (isset($nonRewrittenKeys[$key])) {
259
                    continue;
260
                }
261
                $options['master'][$key] = $value;
262
                unset($options[$key]);
263
            }
264
            if (empty($options['wrapperClass'])) {
265
                // Change the wrapper class only if the user does not already forced using a custom one.
266
                $options['wrapperClass'] = 'Doctrine\\DBAL\\Connections\\MasterSlaveConnection';
267
            }
268
        } else {
269
            unset($options['slaves']);
270
        }
271
272
        if (! empty($options['shards'])) {
273
            $nonRewrittenKeys = [
274
                'driver' => true,
275
                'driverOptions' => true,
276
                'driverClass' => true,
277
                'wrapperClass' => true,
278
                'keepSlave' => true,
279
                'shardChoser' => true,
280
                'platform' => true,
281
                'slaves' => true,
282
                'global' => true,
283
                'shards' => true,
284
                'serverVersion' => true,
285
                // included by safety but should have been unset already
286
                'logging' => true,
287
                'profiling' => true,
288
                'mapping_types' => true,
289
                'platform_service' => true,
290
            ];
291 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...
292
                if (isset($nonRewrittenKeys[$key])) {
293
                    continue;
294
                }
295
                $options['global'][$key] = $value;
296
                unset($options[$key]);
297
            }
298
            if (empty($options['wrapperClass'])) {
299
                // Change the wrapper class only if the user does not already forced using a custom one.
300
                $options['wrapperClass'] = 'Doctrine\\DBAL\\Sharding\\PoolingShardConnection';
301
            }
302
            if (empty($options['shardManagerClass'])) {
303
                // Change the shard manager class only if the user does not already forced using a custom one.
304
                $options['shardManagerClass'] = 'Doctrine\\DBAL\\Sharding\\PoolingShardManager';
305
            }
306
        } else {
307
            unset($options['shards']);
308
        }
309
310
        return $options;
311
    }
312
313
    /**
314
     * Loads the Doctrine ORM configuration.
315
     *
316
     * Usage example:
317
     *
318
     *     <doctrine:orm id="mydm" connection="myconn" />
319
     *
320
     * @param array            $config    An array of configuration settings
321
     * @param ContainerBuilder $container A ContainerBuilder instance
322
     */
323
    protected function ormLoad(array $config, ContainerBuilder $container)
324
    {
325
        $loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
326
        $loader->load('orm.xml');
327
328
        if (class_exists(AbstractType::class) && method_exists(DoctrineType::class, 'reset')) {
329
            $container->getDefinition('form.type.entity')->addTag('kernel.reset', ['method' => 'reset']);
330
        }
331
332
        $entityManagers = [];
333
        foreach (array_keys($config['entity_managers']) as $name) {
334
            $entityManagers[$name] = sprintf('doctrine.orm.%s_entity_manager', $name);
335
        }
336
        $container->setParameter('doctrine.entity_managers', $entityManagers);
337
338
        if (empty($config['default_entity_manager'])) {
339
            $tmp                              = array_keys($entityManagers);
340
            $config['default_entity_manager'] = reset($tmp);
341
        }
342
        $container->setParameter('doctrine.default_entity_manager', $config['default_entity_manager']);
343
344
        $options = ['auto_generate_proxy_classes', 'proxy_dir', 'proxy_namespace'];
345
        foreach ($options as $key) {
346
            $container->setParameter('doctrine.orm.' . $key, $config[$key]);
347
        }
348
349
        $container->setAlias('doctrine.orm.entity_manager', sprintf('doctrine.orm.%s_entity_manager', $config['default_entity_manager']));
350
        $container->getAlias('doctrine.orm.entity_manager')->setPublic(true);
351
352
        $config['entity_managers'] = $this->fixManagersAutoMappings($config['entity_managers'], $container->getParameter('kernel.bundles'));
353
354
        $loadPropertyInfoExtractor = interface_exists('Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface')
355
            && class_exists('Symfony\Bridge\Doctrine\PropertyInfo\DoctrineExtractor');
356
357
        $loadValidatorAutoMappingLoader = class_exists(DoctrineLoader::class);
358
359
        foreach ($config['entity_managers'] as $name => $entityManager) {
360
            $entityManager['name'] = $name;
361
            $this->loadOrmEntityManager($entityManager, $container);
362
363
            if ($loadPropertyInfoExtractor) {
364
                $this->loadPropertyInfoExtractor($name, $container);
365
            }
366
367
            if ($loadValidatorAutoMappingLoader) {
368
                $this->loadValidatorAutoMappingLoader($name, $container);
369
            }
370
        }
371
372
        if ($config['resolve_target_entities']) {
373
            $def = $container->findDefinition('doctrine.orm.listeners.resolve_target_entity');
374
            foreach ($config['resolve_target_entities'] as $name => $implementation) {
375
                $def->addMethodCall('addResolveTargetEntity', [
376
                    $name,
377
                    $implementation,
378
                    [],
379
                ]);
380
            }
381
382
            // BC: ResolveTargetEntityListener implements the subscriber interface since
383
            // v2.5.0-beta1 (Commit 437f812)
0 ignored issues
show
Unused Code Comprehensibility introduced by
39% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

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