Completed
Pull Request — master (#939)
by Andreas
02:09
created

DoctrineExtension::getMappingResourceExtension()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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