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

DoctrineExtension::ormLoad()   B

Complexity

Conditions 9
Paths 64

Size

Total Lines 59

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 59
rs 7.3389
c 0
b 0
f 0
cc 9
nc 64
nop 2

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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