Completed
Push — master ( eb88c4...8af32b )
by Andreas
02:03
created

DoctrineExtension::createPoolCacheDefinition()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

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