Completed
Pull Request — master (#948)
by Andreas
08:55
created

DoctrineExtension::loadMessengerServices()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

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