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

DoctrineExtension::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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