Completed
Pull Request — master (#502)
by Kévin
10:18
created

DoctrineExtension::loadPropertyInfoExtractor()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 16
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 16
rs 9.4286
cc 1
eloc 11
nc 1
nop 2
1
<?php
2
3
/*
4
 * This file is part of the Doctrine Bundle
5
 *
6
 * The code was originally distributed inside the Symfony framework.
7
 *
8
 * (c) Fabien Potencier <[email protected]>
9
 * (c) Doctrine Project, Benjamin Eberlei <[email protected]>
10
 *
11
 * For the full copyright and license information, please view the LICENSE
12
 * file that was distributed with this source code.
13
 */
14
15
namespace Doctrine\Bundle\DoctrineBundle\DependencyInjection;
16
17
use Doctrine\ORM\Version;
18
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
19
use Symfony\Component\DependencyInjection\Alias;
20
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
21
use Symfony\Component\DependencyInjection\ContainerBuilder;
22
use Symfony\Component\DependencyInjection\Definition;
23
use Symfony\Component\DependencyInjection\DefinitionDecorator;
24
use Symfony\Component\DependencyInjection\Reference;
25
use Symfony\Bridge\Doctrine\DependencyInjection\AbstractDoctrineExtension;
26
use Symfony\Component\Config\FileLocator;
27
use Doctrine\Bundle\DoctrineCacheBundle\DependencyInjection\SymfonyBridgeAdapter;
28
use Doctrine\Bundle\DoctrineCacheBundle\DependencyInjection\CacheProviderLoader;
29
30
/**
31
 * DoctrineExtension is an extension for the Doctrine DBAL and ORM library.
32
 *
33
 * @author Jonathan H. Wage <[email protected]>
34
 * @author Fabien Potencier <[email protected]>
35
 * @author Benjamin Eberlei <[email protected]>
36
 * @author Fabio B. Silva <[email protected]>
37
 * @author Kinn Coelho Julião <[email protected]>
38
 */
39
class DoctrineExtension extends AbstractDoctrineExtension
40
{
41
    /**
42
     * @var string
43
     */
44
    private $defaultConnection;
45
46
    /**
47
     * @var array
48
     */
49
    private $entityManagers;
50
51
    /**
52
     * @var SymfonyBridgeAdapter
53
     */
54
    private $adapter;
55
56
    /**
57
     * @param SymfonyBridgeAdapter $adapter
0 ignored issues
show
Documentation introduced by
Should the type for parameter $adapter not be null|SymfonyBridgeAdapter?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
58
     */
59
    public function __construct(SymfonyBridgeAdapter $adapter = null)
60
    {
61
        $this->adapter = $adapter ?: new SymfonyBridgeAdapter(new CacheProviderLoader(), 'doctrine.orm', 'orm');
62
    }
63
64
    /**
65
     * {@inheritDoc}
66
     */
67
    public function load(array $configs, ContainerBuilder $container)
68
    {
69
        $configuration = $this->getConfiguration($configs, $container);
70
        $config = $this->processConfiguration($configuration, $configs);
71
72
        $this->adapter->loadServicesConfiguration($container);
73
74
        if (!empty($config['dbal'])) {
75
            $this->dbalLoad($config['dbal'], $container);
76
        }
77
78
        if (!empty($config['orm'])) {
79
            if (empty($config['dbal'])) {
80
                throw new \LogicException('Configuring the ORM layer requires to configure the DBAL layer as well.');
81
            }
82
83
            $this->ormLoad($config['orm'], $container);
84
        }
85
86
        $this->addClassesToCompile(array(
87
            'Doctrine\\Common\\Annotations\\DocLexer',
88
            'Doctrine\\Common\\Annotations\\FileCacheReader',
89
            'Doctrine\\Common\\Annotations\\PhpParser',
90
            'Doctrine\\Common\\Annotations\\Reader',
91
            'Doctrine\\Common\\Lexer',
92
            'Doctrine\\Common\\Persistence\\ConnectionRegistry',
93
            'Doctrine\\Common\\Persistence\\Proxy',
94
            'Doctrine\\Common\\Util\\ClassUtils',
95
            'Doctrine\\Bundle\\DoctrineBundle\\Registry',
96
        ));
97
    }
98
99
    /**
100
     * Loads the DBAL configuration.
101
     *
102
     * Usage example:
103
     *
104
     *      <doctrine:dbal id="myconn" dbname="sfweb" user="root" />
105
     *
106
     * @param array            $config    An array of configuration settings
107
     * @param ContainerBuilder $container A ContainerBuilder instance
108
     */
109
    protected function dbalLoad(array $config, ContainerBuilder $container)
110
    {
111
        $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
112
        $loader->load('dbal.xml');
113
114
        if (empty($config['default_connection'])) {
115
            $keys = array_keys($config['connections']);
116
            $config['default_connection'] = reset($keys);
117
        }
118
119
        $this->defaultConnection = $config['default_connection'];
120
121
        $container->setAlias('database_connection', sprintf('doctrine.dbal.%s_connection', $this->defaultConnection));
122
        $container->setAlias('doctrine.dbal.event_manager', new Alias(sprintf('doctrine.dbal.%s_connection.event_manager', $this->defaultConnection), false));
123
124
        $container->setParameter('doctrine.dbal.connection_factory.types', $config['types']);
125
126
        $connections = array();
127
128
        foreach (array_keys($config['connections']) as $name) {
129
            $connections[$name] = sprintf('doctrine.dbal.%s_connection', $name);
130
        }
131
132
        $container->setParameter('doctrine.connections', $connections);
133
        $container->setParameter('doctrine.default_connection', $this->defaultConnection);
134
135
        $def = $container->getDefinition('doctrine.dbal.connection');
136
        if (method_exists($def, 'setFactory')) {
137
            // to be inlined in dbal.xml when dependency on Symfony DependencyInjection is bumped to 2.6
138
            $def->setFactory(array(new Reference('doctrine.dbal.connection_factory'), 'createConnection'));
139
        } else {
140
            // to be removed when dependency on Symfony DependencyInjection is bumped to 2.6
141
            $def->setFactoryService('doctrine.dbal.connection_factory');
0 ignored issues
show
Bug introduced by
The method setFactoryService() does not exist on Symfony\Component\DependencyInjection\Definition. Did you maybe mean setFactory()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
142
            $def->setFactoryMethod('createConnection');
0 ignored issues
show
Bug introduced by
The method setFactoryMethod() does not exist on Symfony\Component\DependencyInjection\Definition. Did you maybe mean setFactory()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
143
        }
144
145
        foreach ($config['connections'] as $name => $connection) {
146
            $this->loadDbalConnection($name, $connection, $container);
147
        }
148
    }
149
150
    /**
151
     * Loads a configured DBAL connection.
152
     *
153
     * @param string           $name       The name of the connection
154
     * @param array            $connection A dbal connection configuration.
155
     * @param ContainerBuilder $container  A ContainerBuilder instance
156
     */
157
    protected function loadDbalConnection($name, array $connection, ContainerBuilder $container)
158
    {
159
        // configuration
160
        $configuration = $container->setDefinition(sprintf('doctrine.dbal.%s_connection.configuration', $name), new DefinitionDecorator('doctrine.dbal.connection.configuration'));
161
        $logger = null;
162
        if ($connection['logging']) {
163
            $logger = new Reference('doctrine.dbal.logger');
164
        }
165
        unset ($connection['logging']);
166
        if ($connection['profiling']) {
167
            $profilingLoggerId = 'doctrine.dbal.logger.profiling.'.$name;
168
            $container->setDefinition($profilingLoggerId, new DefinitionDecorator('doctrine.dbal.logger.profiling'));
169
            $profilingLogger = new Reference($profilingLoggerId);
170
            $container->getDefinition('data_collector.doctrine')->addMethodCall('addLogger', array($name, $profilingLogger));
171
172
            if (null !== $logger) {
173
                $chainLogger = new DefinitionDecorator('doctrine.dbal.logger.chain');
174
                $chainLogger->addMethodCall('addLogger', array($profilingLogger));
175
176
                $loggerId = 'doctrine.dbal.logger.chain.'.$name;
177
                $container->setDefinition($loggerId, $chainLogger);
178
                $logger = new Reference($loggerId);
179
            } else {
180
                $logger = $profilingLogger;
181
            }
182
        }
183
        unset($connection['profiling']);
184
185
        if (isset($connection['auto_commit'])) {
186
            $configuration->addMethodCall('setAutoCommit', array($connection['auto_commit']));
187
        }
188
189
        unset($connection['auto_commit']);
190
191
        if (isset($connection['schema_filter']) && $connection['schema_filter']) {
192
            $configuration->addMethodCall('setFilterSchemaAssetsExpression', array($connection['schema_filter']));
193
        }
194
195
        unset($connection['schema_filter']);
196
197
        if ($logger) {
198
            $configuration->addMethodCall('setSQLLogger', array($logger));
199
        }
200
201
        // event manager
202
        $container->setDefinition(sprintf('doctrine.dbal.%s_connection.event_manager', $name), new DefinitionDecorator('doctrine.dbal.connection.event_manager'));
203
204
        // connection
205
        // PDO ignores the charset property before 5.3.6 so the init listener has to be used instead.
206
        if (isset($connection['charset']) && version_compare(PHP_VERSION, '5.3.6', '<')) {
207
            if ((isset($connection['driver']) && stripos($connection['driver'], 'mysql') !== false) ||
208
                 (isset($connection['driver_class']) && stripos($connection['driver_class'], 'mysql') !== false)) {
209
                $mysqlSessionInit = new Definition('%doctrine.dbal.events.mysql_session_init.class%');
210
                $mysqlSessionInit->setArguments(array($connection['charset']));
211
                $mysqlSessionInit->setPublic(false);
212
                $mysqlSessionInit->addTag('doctrine.event_subscriber', array('connection' => $name));
213
214
                $container->setDefinition(
215
                    sprintf('doctrine.dbal.%s_connection.events.mysqlsessioninit', $name),
216
                    $mysqlSessionInit
217
                );
218
                unset($connection['charset']);
219
            }
220
        }
221
222
        $options = $this->getConnectionOptions($connection);
223
224
        $def = $container
225
            ->setDefinition(sprintf('doctrine.dbal.%s_connection', $name), new DefinitionDecorator('doctrine.dbal.connection'))
226
            ->setArguments(array(
227
                $options,
228
                new Reference(sprintf('doctrine.dbal.%s_connection.configuration', $name)),
229
                new Reference(sprintf('doctrine.dbal.%s_connection.event_manager', $name)),
230
                $connection['mapping_types'],
231
            ))
232
        ;
233
234
        if (!empty($connection['use_savepoints'])) {
235
            $def->addMethodCall('setNestTransactionsWithSavepoints', array($connection['use_savepoints']));
236
        }
237
    }
238
239
    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...
240
    {
241
        $options = $connection;
242
243
        if (isset($options['platform_service'])) {
244
            $options['platform'] = new Reference($options['platform_service']);
245
            unset($options['platform_service']);
246
        }
247
        unset($options['mapping_types']);
248
249
        if (isset($options['shard_choser_service'])) {
250
            $options['shard_choser'] = new Reference($options['shard_choser_service']);
251
            unset($options['shard_choser_service']);
252
        }
253
254
        foreach (array(
255
            'options' => 'driverOptions',
256
            'driver_class' => 'driverClass',
257
            'wrapper_class' => 'wrapperClass',
258
            'keep_slave' => 'keepSlave',
259
            'shard_choser' => 'shardChoser',
260
            'server_version' => 'serverVersion',
261
            'default_table_options' => 'defaultTableOptions',
262
        ) as $old => $new) {
263
            if (isset($options[$old])) {
264
                $options[$new] = $options[$old];
265
                unset($options[$old]);
266
            }
267
        }
268
269
        if (!empty($options['slaves']) && !empty($options['shards'])) {
270
            throw new InvalidArgumentException('Sharding and master-slave connection cannot be used together');
271
        }
272
273 View Code Duplication
        if (!empty($options['slaves'])) {
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...
274
            $nonRewrittenKeys = array(
275
                'driver' => true, 'driverOptions' => true, 'driverClass' => true,
276
                'wrapperClass' => true, 'keepSlave' => true, 'shardChoser' => true,
277
                'platform' => true, 'slaves' => true, 'master' => true, 'shards' => true,
278
                'serverVersion' => true,
279
                // included by safety but should have been unset already
280
                'logging' => true, 'profiling' => true, 'mapping_types' => true, 'platform_service' => true,
281
            );
282
            foreach ($options as $key => $value) {
283
                if (isset($nonRewrittenKeys[$key])) {
284
                    continue;
285
                }
286
                $options['master'][$key] = $value;
287
                unset($options[$key]);
288
            }
289
            if (empty($options['wrapperClass'])) {
290
                // Change the wrapper class only if the user does not already forced using a custom one.
291
                $options['wrapperClass'] = 'Doctrine\\DBAL\\Connections\\MasterSlaveConnection';
292
            }
293
        } else {
294
            unset($options['slaves']);
295
        }
296
297 View Code Duplication
        if (!empty($options['shards'])) {
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...
298
            $nonRewrittenKeys = array(
299
                'driver' => true, 'driverOptions' => true, 'driverClass' => true,
300
                'wrapperClass' => true, 'keepSlave' => true, 'shardChoser' => true,
301
                'platform' => true, 'slaves' => true, 'global' => true, 'shards' => true,
302
                // included by safety but should have been unset already
303
                'logging' => true, 'profiling' => true, 'mapping_types' => true, 'platform_service' => true,
304
            );
305
            foreach ($options as $key => $value) {
306
                if (isset($nonRewrittenKeys[$key])) {
307
                    continue;
308
                }
309
                $options['global'][$key] = $value;
310
                unset($options[$key]);
311
            }
312
            if (empty($options['wrapperClass'])) {
313
                // Change the wrapper class only if the user does not already forced using a custom one.
314
                $options['wrapperClass'] = 'Doctrine\\DBAL\\Sharding\\PoolingShardConnection';
315
            }
316
        } else {
317
            unset($options['shards']);
318
        }
319
320
        return $options;
321
    }
322
323
    /**
324
     * Loads the Doctrine ORM configuration.
325
     *
326
     * Usage example:
327
     *
328
     *     <doctrine:orm id="mydm" connection="myconn" />
329
     *
330
     * @param array            $config    An array of configuration settings
331
     * @param ContainerBuilder $container A ContainerBuilder instance
332
     */
333
    protected function ormLoad(array $config, ContainerBuilder $container)
334
    {
335
        $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
336
        $loader->load('orm.xml');
337
338
        $this->entityManagers = array();
339
        foreach (array_keys($config['entity_managers']) as $name) {
340
            $this->entityManagers[$name] = sprintf('doctrine.orm.%s_entity_manager', $name);
341
        }
342
        $container->setParameter('doctrine.entity_managers', $this->entityManagers);
343
344
        if (empty($config['default_entity_manager'])) {
345
            $tmp = array_keys($this->entityManagers);
346
            $config['default_entity_manager'] = reset($tmp);
347
        }
348
        $container->setParameter('doctrine.default_entity_manager', $config['default_entity_manager']);
349
350
        $options = array('auto_generate_proxy_classes', 'proxy_dir', 'proxy_namespace');
351
        foreach ($options as $key) {
352
            $container->setParameter('doctrine.orm.'.$key, $config[$key]);
353
        }
354
355
        $container->setAlias('doctrine.orm.entity_manager', sprintf('doctrine.orm.%s_entity_manager', $config['default_entity_manager']));
356
357
        // BC logic to handle DoctrineBridge < 2.6
358
        if (!method_exists($this, 'fixManagersAutoMappings')) {
359
            foreach ($config['entity_managers'] as $entityManager) {
360
                if ($entityManager['auto_mapping'] && count($config['entity_managers']) > 1) {
361
                    throw new \LogicException('You cannot enable "auto_mapping" when several entity managers are defined.');
362
                }
363
            }
364
        } else {
365
            $config['entity_managers'] = $this->fixManagersAutoMappings($config['entity_managers'], $container->getParameter('kernel.bundles'));
366
        }
367
368
        $def = $container->getDefinition('doctrine.orm.entity_manager.abstract');
369
        if (method_exists($def, 'setFactory')) {
370
            // to be inlined in dbal.xml when dependency on Symfony DependencyInjection is bumped to 2.6
371
            $def->setFactory(array('%doctrine.orm.entity_manager.class%', 'create'));
372
        } else {
373
            // to be removed when dependency on Symfony DependencyInjection is bumped to 2.6
374
            $def->setFactoryClass('%doctrine.orm.entity_manager.class%');
0 ignored issues
show
Bug introduced by
The method setFactoryClass() does not exist on Symfony\Component\DependencyInjection\Definition. Did you maybe mean setFactory()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
375
            $def->setFactoryMethod('create');
0 ignored issues
show
Bug introduced by
The method setFactoryMethod() does not exist on Symfony\Component\DependencyInjection\Definition. Did you maybe mean setFactory()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
376
        }
377
378
        foreach ($config['entity_managers'] as $name => $entityManager) {
379
            $entityManager['name'] = $name;
380
            $this->loadOrmEntityManager($entityManager, $container);
381
        }
382
383
        if ($config['resolve_target_entities']) {
384
            $def = $container->findDefinition('doctrine.orm.listeners.resolve_target_entity');
385
            foreach ($config['resolve_target_entities'] as $name => $implementation) {
386
                $def->addMethodCall('addResolveTargetEntity', array(
387
                    $name, $implementation, array(),
388
                ));
389
            }
390
391
            // BC: ResolveTargetEntityListener implements the subscriber interface since
392
            // v2.5.0-beta1 (Commit 437f812)
0 ignored issues
show
Unused Code Comprehensibility introduced by
39% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
393
            if (version_compare(Version::VERSION, '2.5.0-DEV') < 0) {
394
                $def->addTag('doctrine.event_listener', array('event' => 'loadClassMetadata'));
395
            } else {
396
                $def->addTag('doctrine.event_subscriber');
397
            }
398
        }
399
400
        if (!class_exists('Symfony\Bridge\Doctrine\PropertyInfo\DoctrineExtractor')) {
401
            return;
402
        }
403
404
        foreach ($config['entity_managers'] as $name => $entityManager) {
405
            $this->loadPropertyInfoExtractor($name, $container);
406
        }
407
    }
408
409
    /**
410
     * Loads a configured ORM entity manager.
411
     *
412
     * @param array            $entityManager A configured ORM entity manager.
413
     * @param ContainerBuilder $container     A ContainerBuilder instance
414
     */
415
    protected function loadOrmEntityManager(array $entityManager, ContainerBuilder $container)
416
    {
417
        $ormConfigDef = $container->setDefinition(sprintf('doctrine.orm.%s_configuration', $entityManager['name']), new DefinitionDecorator('doctrine.orm.configuration'));
418
419
        $this->loadOrmEntityManagerMappingInformation($entityManager, $ormConfigDef, $container);
420
        $this->loadOrmCacheDrivers($entityManager, $container);
421
422
        if (isset($entityManager['entity_listener_resolver']) && $entityManager['entity_listener_resolver']) {
423
            $container->setAlias(sprintf('doctrine.orm.%s_entity_listener_resolver', $entityManager['name']), $entityManager['entity_listener_resolver']);
424
        } else {
425
            $container->setDefinition(sprintf('doctrine.orm.%s_entity_listener_resolver', $entityManager['name']), new Definition('%doctrine.orm.entity_listener_resolver.class%'));
426
        }
427
428
        $methods = array(
429
            'setMetadataCacheImpl' => new Reference(sprintf('doctrine.orm.%s_metadata_cache', $entityManager['name'])),
430
            'setQueryCacheImpl' => new Reference(sprintf('doctrine.orm.%s_query_cache', $entityManager['name'])),
431
            'setResultCacheImpl' => new Reference(sprintf('doctrine.orm.%s_result_cache', $entityManager['name'])),
432
            'setMetadataDriverImpl' => new Reference('doctrine.orm.'.$entityManager['name'].'_metadata_driver'),
433
            'setProxyDir' => '%doctrine.orm.proxy_dir%',
434
            'setProxyNamespace' => '%doctrine.orm.proxy_namespace%',
435
            'setAutoGenerateProxyClasses' => '%doctrine.orm.auto_generate_proxy_classes%',
436
            'setClassMetadataFactoryName' => $entityManager['class_metadata_factory_name'],
437
            'setDefaultRepositoryClassName' => $entityManager['default_repository_class'],
438
        );
439
        // check for version to keep BC
440
        if (version_compare(Version::VERSION, "2.3.0-DEV") >= 0) {
441
            $methods = array_merge($methods, array(
442
                'setNamingStrategy' => new Reference($entityManager['naming_strategy']),
443
                'setQuoteStrategy' => new Reference($entityManager['quote_strategy']),
444
            ));
445
        }
446
447
        if (version_compare(Version::VERSION, "2.4.0-DEV") >= 0) {
448
            $methods = array_merge($methods, array(
449
                'setEntityListenerResolver' => new Reference(sprintf('doctrine.orm.%s_entity_listener_resolver', $entityManager['name'])),
450
            ));
451
        }
452
453
        if (version_compare(Version::VERSION, "2.5.0-DEV") >= 0) {
454
            $listenerId = sprintf('doctrine.orm.%s_listeners.attach_entity_listeners', $entityManager['name']);
455
            $listenerDef = $container->setDefinition($listenerId, new Definition('%doctrine.orm.listeners.attach_entity_listeners.class%'));
456
            $listenerDef->addTag('doctrine.event_listener', array('event' => 'loadClassMetadata'));
457
        }
458
459
        if (isset($entityManager['second_level_cache'])) {
460
            $this->loadOrmSecondLevelCache($entityManager, $ormConfigDef, $container);
461
        }
462
463
        if ($entityManager['repository_factory']) {
464
            $methods['setRepositoryFactory'] = new Reference($entityManager['repository_factory']);
465
        }
466
467
        foreach ($methods as $method => $arg) {
468
            $ormConfigDef->addMethodCall($method, array($arg));
469
        }
470
471
        foreach ($entityManager['hydrators'] as $name => $class) {
472
            $ormConfigDef->addMethodCall('addCustomHydrationMode', array($name, $class));
473
        }
474
475
        if (!empty($entityManager['dql'])) {
476
            foreach ($entityManager['dql']['string_functions'] as $name => $function) {
477
                $ormConfigDef->addMethodCall('addCustomStringFunction', array($name, $function));
478
            }
479
            foreach ($entityManager['dql']['numeric_functions'] as $name => $function) {
480
                $ormConfigDef->addMethodCall('addCustomNumericFunction', array($name, $function));
481
            }
482
            foreach ($entityManager['dql']['datetime_functions'] as $name => $function) {
483
                $ormConfigDef->addMethodCall('addCustomDatetimeFunction', array($name, $function));
484
            }
485
        }
486
487
        $enabledFilters = array();
488
        $filtersParameters = array();
489
        foreach ($entityManager['filters'] as $name => $filter) {
490
            $ormConfigDef->addMethodCall('addFilter', array($name, $filter['class']));
491
            if ($filter['enabled']) {
492
                $enabledFilters[] = $name;
493
            }
494
            if ($filter['parameters']) {
495
                $filtersParameters[$name] = $filter['parameters'];
496
            }
497
        }
498
499
        $managerConfiguratorName = sprintf('doctrine.orm.%s_manager_configurator', $entityManager['name']);
500
        $container
501
            ->setDefinition($managerConfiguratorName, new DefinitionDecorator('doctrine.orm.manager_configurator.abstract'))
502
            ->replaceArgument(0, $enabledFilters)
503
            ->replaceArgument(1, $filtersParameters)
504
        ;
505
506
        if (!isset($entityManager['connection'])) {
507
            $entityManager['connection'] = $this->defaultConnection;
508
        }
509
510
        $container
511
            ->setDefinition(sprintf('doctrine.orm.%s_entity_manager', $entityManager['name']), new DefinitionDecorator('doctrine.orm.entity_manager.abstract'))
512
            ->setArguments(array(
513
                new Reference(sprintf('doctrine.dbal.%s_connection', $entityManager['connection'])),
514
                new Reference(sprintf('doctrine.orm.%s_configuration', $entityManager['name'])),
515
            ))
516
            ->setConfigurator(array(new Reference($managerConfiguratorName), 'configure'))
517
        ;
518
519
        $container->setAlias(
520
            sprintf('doctrine.orm.%s_entity_manager.event_manager', $entityManager['name']),
521
            new Alias(sprintf('doctrine.dbal.%s_connection.event_manager', $entityManager['connection']), false)
522
        );
523
524
        if (isset($entityManager['entity_listeners'])) {
525
            if (!isset($listenerDef)) {
526
                throw new InvalidArgumentException('Entity listeners configuration requires doctrine-orm 2.5.0 or newer');
527
            }
528
529
            $entities = $entityManager['entity_listeners']['entities'];
530
531
            foreach ($entities as $entityListenerClass => $entity) {
532
                foreach ($entity['listeners'] as $listenerClass => $listener) {
533
                    foreach ($listener['events'] as $listenerEvent) {
534
                        $listenerEventName = $listenerEvent['type'];
535
                        $listenerMethod = $listenerEvent['method'];
536
537
                        $listenerDef->addMethodCall('addEntityListener', array(
538
                            $entityListenerClass, $listenerClass, $listenerEventName, $listenerMethod,
539
                        ));
540
                    }
541
                }
542
            }
543
544
        }
545
    }
546
547
    /**
548
     * Loads an ORM entity managers bundle mapping information.
549
     *
550
     * There are two distinct configuration possibilities for mapping information:
551
     *
552
     * 1. Specify a bundle and optionally details where the entity and mapping information reside.
553
     * 2. Specify an arbitrary mapping location.
554
     *
555
     * @example
556
     *
557
     *  doctrine.orm:
558
     *     mappings:
559
     *         MyBundle1: ~
560
     *         MyBundle2: yml
561
     *         MyBundle3: { type: annotation, dir: Entities/ }
562
     *         MyBundle4: { type: xml, dir: Resources/config/doctrine/mapping }
563
     *         MyBundle5:
564
     *             type: yml
565
     *             dir: bundle-mappings/
566
     *             alias: BundleAlias
567
     *         arbitrary_key:
568
     *             type: xml
569
     *             dir: %kernel.root_dir%/../src/vendor/DoctrineExtensions/lib/DoctrineExtensions/Entities
570
     *             prefix: DoctrineExtensions\Entities\
571
     *             alias: DExt
572
     *
573
     * In the case of bundles everything is really optional (which leads to autodetection for this bundle) but
574
     * in the mappings key everything except alias is a required argument.
575
     *
576
     * @param array            $entityManager A configured ORM entity manager
577
     * @param Definition       $ormConfigDef  A Definition instance
578
     * @param ContainerBuilder $container     A ContainerBuilder instance
579
     */
580
    protected function loadOrmEntityManagerMappingInformation(array $entityManager, Definition $ormConfigDef, ContainerBuilder $container)
581
    {
582
        // reset state of drivers and alias map. They are only used by this methods and children.
583
        $this->drivers = array();
584
        $this->aliasMap = array();
585
586
        $this->loadMappingInformation($entityManager, $container);
587
        $this->registerMappingDrivers($entityManager, $container);
588
589
        $ormConfigDef->addMethodCall('setEntityNamespaces', array($this->aliasMap));
590
    }
591
592
    /**
593
     * Loads an ORM second level cache bundle mapping information.
594
     *
595
     * @example
596
     *  entity_managers:
597
     *      default:
598
     *          second_level_cache:
599
     *              region_cache_driver: apc
600
     *              log_enabled: true
601
     *              regions:
602
     *                  my_service_region:
603
     *                      type: service
604
     *                      service : "my_service_region"
605
     *
606
     *                  my_query_region:
607
     *                      lifetime: 300
608
     *                      cache_driver: array
609
     *                      type: filelock
610
     *
611
     *                  my_entity_region:
612
     *                      lifetime: 600
613
     *                      cache_driver:
614
     *                          type: apc
615
     *
616
     * @param array            $entityManager A configured ORM entity manager
617
     * @param Definition       $ormConfigDef  A Definition instance
618
     * @param ContainerBuilder $container     A ContainerBuilder instance
619
     */
620
    protected function loadOrmSecondLevelCache(array $entityManager, Definition $ormConfigDef, ContainerBuilder $container)
621
    {
622
        if (version_compare(Version::VERSION, '2.5.0-DEV') < 0) {
623
            throw new \InvalidArgumentException('Second-level cache requires doctrine-orm 2.5.0 or newer');
624
        }
625
626
        $driverId = null;
627
        $enabled = $entityManager['second_level_cache']['enabled'];
628
629
        if (isset($entityManager['second_level_cache']['region_cache_driver'])) {
630
            $driverName = 'second_level_cache.region_cache_driver';
631
            $driverMap = $entityManager['second_level_cache']['region_cache_driver'];
632
            $driverId = $this->loadCacheDriver($driverName, $entityManager['name'], $driverMap, $container);
633
        }
634
635
        $configId = sprintf('doctrine.orm.%s_second_level_cache.cache_configuration', $entityManager['name']);
636
        $regionsId = sprintf('doctrine.orm.%s_second_level_cache.regions_configuration', $entityManager['name']);
637
        $driverId = $driverId ?: sprintf('doctrine.orm.%s_second_level_cache.region_cache_driver', $entityManager['name']);
638
        $configDef = $container->setDefinition($configId, new Definition('%doctrine.orm.second_level_cache.cache_configuration.class%'));
639
        $regionsDef = $container->setDefinition($regionsId, new Definition('%doctrine.orm.second_level_cache.regions_configuration.class%'));
640
641
        $slcFactoryId = sprintf('doctrine.orm.%s_second_level_cache.default_cache_factory', $entityManager['name']);
642
        $slcFactoryDef = $container
643
            ->setDefinition($slcFactoryId, new Definition('%doctrine.orm.second_level_cache.default_cache_factory.class%'))
644
            ->setArguments(array(new Reference($regionsId), new Reference($driverId)));
645
646
        if (isset($entityManager['second_level_cache']['regions'])) {
647
            foreach ($entityManager['second_level_cache']['regions'] as $name => $region) {
648
                $regionRef = null;
649
                $regionType = $region['type'];
650
651
                if ($regionType === 'service') {
652
                    $regionId = sprintf('doctrine.orm.%s_second_level_cache.region.%s', $entityManager['name'], $name);
653
                    $regionRef = new Reference($region['service']);
654
655
                    $container->setAlias($regionId, new Alias($region['service'], false));
656
                }
657
658
                if ($regionType === 'default' || $regionType === 'filelock') {
659
                    $regionId = sprintf('doctrine.orm.%s_second_level_cache.region.%s', $entityManager['name'], $name);
660
                    $driverName = sprintf('second_level_cache.region.%s_driver', $name);
661
                    $driverMap = $region['cache_driver'];
662
                    $driverId = $this->loadCacheDriver($driverName, $entityManager['name'], $driverMap, $container);
663
                    $regionRef = new Reference($regionId);
664
665
                    $container
666
                        ->setDefinition($regionId, new Definition('%doctrine.orm.second_level_cache.default_region.class%'))
667
                        ->setArguments(array($name, new Reference($driverId), $region['lifetime']));
668
                }
669
670
                if ($regionType === 'filelock') {
671
                    $regionId = sprintf('doctrine.orm.%s_second_level_cache.region.%s_filelock', $entityManager['name'], $name);
672
673
                    $container
674
                        ->setDefinition($regionId, new Definition('%doctrine.orm.second_level_cache.filelock_region.class%'))
675
                        ->setArguments(array($regionRef, $region['lock_path'], $region['lock_lifetime']));
676
677
                    $regionRef = new Reference($regionId);
678
                    $regionsDef->addMethodCall('getLockLifetime', array($name, $region['lock_lifetime']));
679
                }
680
681
                $regionsDef->addMethodCall('setLifetime', array($name, $region['lifetime']));
682
                $slcFactoryDef->addMethodCall('setRegion', array($regionRef));
683
            }
684
        }
685
686
        if ($entityManager['second_level_cache']['log_enabled']) {
687
            $loggerChainId = sprintf('doctrine.orm.%s_second_level_cache.logger_chain', $entityManager['name']);
688
            $loggerStatsId = sprintf('doctrine.orm.%s_second_level_cache.logger_statistics', $entityManager['name']);
689
            $loggerChaingDef = $container->setDefinition($loggerChainId, new Definition('%doctrine.orm.second_level_cache.logger_chain.class%'));
690
            $loggerStatsDef = $container->setDefinition($loggerStatsId, new Definition('%doctrine.orm.second_level_cache.logger_statistics.class%'));
691
692
            $loggerChaingDef->addMethodCall('setLogger', array('statistics', $loggerStatsDef));
693
            $configDef->addMethodCall('setCacheLogger', array($loggerChaingDef));
694
695
            foreach ($entityManager['second_level_cache']['loggers'] as $name => $logger) {
696
                $loggerId = sprintf('doctrine.orm.%s_second_level_cache.logger.%s', $entityManager['name'], $name);
697
                $loggerRef = new Reference($logger['service']);
698
699
                $container->setAlias($loggerId, new Alias($logger['service'], false));
700
                $loggerChaingDef->addMethodCall('setLogger', array($name, $loggerRef));
701
            }
702
        }
703
704
        $configDef->addMethodCall('setCacheFactory', array($slcFactoryDef));
705
        $configDef->addMethodCall('setRegionsConfiguration', array($regionsDef));
706
        $ormConfigDef->addMethodCall('setSecondLevelCacheEnabled', array($enabled));
707
        $ormConfigDef->addMethodCall('setSecondLevelCacheConfiguration', array($configDef));
708
    }
709
710
    /**
711
     * {@inheritDoc}
712
     */
713
    protected function getObjectManagerElementName($name)
714
    {
715
        return 'doctrine.orm.'.$name;
716
    }
717
718
    protected function getMappingObjectDefaultName()
719
    {
720
        return 'Entity';
721
    }
722
723
    /**
724
     * {@inheritDoc}
725
     */
726
    protected function getMappingResourceConfigDirectory()
727
    {
728
        return 'Resources/config/doctrine';
729
    }
730
731
    /**
732
     * {@inheritDoc}
733
     */
734
    protected function getMappingResourceExtension()
735
    {
736
        return 'orm';
737
    }
738
739
    /**
740
     * {@inheritDoc}
741
     */
742
    protected function loadCacheDriver($driverName, $entityManagerName, array $driverMap, ContainerBuilder $container)
743
    {
744
        if (!empty($driverMap['cache_provider'])) {
745
            $aliasId = $this->getObjectManagerElementName(sprintf('%s_%s', $entityManagerName, $driverName));
746
            $serviceId = sprintf('doctrine_cache.providers.%s', $driverMap['cache_provider']);
747
748
            $container->setAlias($aliasId, new Alias($serviceId, false));
749
750
            return $aliasId;
751
        }
752
753
        return $this->adapter->loadCacheDriver($driverName, $entityManagerName, $driverMap, $container);
754
    }
755
756
    /**
757
     * Loads a configured entity managers cache drivers.
758
     *
759
     * @param array            $entityManager A configured ORM entity manager.
760
     * @param ContainerBuilder $container     A ContainerBuilder instance
761
     */
762
    protected function loadOrmCacheDrivers(array $entityManager, ContainerBuilder $container)
763
    {
764
        $this->loadCacheDriver('metadata_cache', $entityManager['name'], $entityManager['metadata_cache_driver'], $container);
765
        $this->loadCacheDriver('result_cache', $entityManager['name'], $entityManager['result_cache_driver'], $container);
766
        $this->loadCacheDriver('query_cache', $entityManager['name'], $entityManager['query_cache_driver'], $container);
767
    }
768
769
    /**
770
     * Loads a property info extractor for each defined entity manager.
771
     *
772
     * @param string           $entityManagerName
773
     * @param ContainerBuilder $container
774
     */
775
    protected function loadPropertyInfoExtractor($entityManagerName, ContainerBuilder $container)
776
    {
777
        $metadataFactoryService = sprintf('doctrine.orm.%s_entity_manager.metadata_factory', $entityManagerName);
778
779
        $metadataFactoryDefinition = $container->register($metadataFactoryService, 'Doctrine\Common\Persistence\Mapping\ClassMetadataFactory');
780
        $metadataFactoryDefinition->setFactory(array(
781
            new Reference(sprintf('doctrine.orm.%s_entity_manager', $entityManagerName)),
782
            'getMetadataFactory'
783
        ));
784
        $metadataFactoryDefinition->setPublic(false);
785
786
        $propertyExtractorDefinition = $container->register(sprintf('doctrine.orm.%s_entity_manager.property_info_extractor', $entityManagerName), 'Symfony\Bridge\Doctrine\PropertyInfo\DoctrineExtractor');
787
        $propertyExtractorDefinition->addArgument(new Reference($metadataFactoryService));
788
        $propertyExtractorDefinition->addTag('property_info.list_extractor', array('priority' => -1001));
789
        $propertyExtractorDefinition->addTag('property_info.type_extractor', array('priority' => -999));
790
    }
791
792
    /**
793
     * @param array            $objectManager
794
     * @param ContainerBuilder $container
795
     * @param string           $cacheName
796
     */
797
    public function loadObjectManagerCacheDriver(array $objectManager, ContainerBuilder $container, $cacheName)
798
    {
799
        $this->loadCacheDriver($cacheName, $objectManager['name'], $objectManager[$cacheName.'_driver'], $container);
800
    }
801
802
    /**
803
     * {@inheritDoc}
804
     */
805
    public function getXsdValidationBasePath()
806
    {
807
        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...
808
    }
809
810
    /**
811
     * {@inheritDoc}
812
     */
813
    public function getNamespace()
814
    {
815
        return 'http://symfony.com/schema/dic/doctrine';
816
    }
817
818
    /**
819
     * {@inheritDoc}
820
     */
821
    public function getConfiguration(array $config, ContainerBuilder $container)
822
    {
823
        return new Configuration($container->getParameter('kernel.debug'));
824
    }
825
}
826