Completed
Push — master ( 05d193...e557e0 )
by Filip
04:55
created

OrmExtension   D

Complexity

Total Complexity 110

Size/Duplication

Total Lines 907
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 0
Metric Value
wmc 110
lcom 1
cbo 5
dl 0
loc 907
rs 4.4444
c 0
b 0
f 0

21 Methods

Rating   Name   Duplication   Size   Complexity  
F processEntityManager() 0 193 18
B processSecondLevelCache() 0 51 5
C loadConfiguration() 0 66 13
A loadConsole() 0 17 3
C processConnection() 0 71 8
C processMetadataDriver() 0 53 16
A processCache() 0 4 1
A beforeCompile() 0 5 1
D processRepositories() 0 81 17
A processEventManagers() 0 12 4
A getServiceBoundManagers() 0 7 2
A afterCompile() 0 17 3
C processRepositoryFactoryEntities() 0 38 8
A evalAndInstantiateContainer() 0 12 1
A resolveConfig() 0 7 1
A normalizeTargetEntityMappings() 0 21 3
A isTracyPresent() 0 4 1
A isKdybyEventsPresent() 0 4 1
A addCollapsePathsToTracy() 0 11 2
A register() 0 6 1
A natSortKeys() 0 8 1

How to fix   Complexity   

Complex Class

Complex classes like OrmExtension often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use OrmExtension, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * This file is part of the Kdyby (http://www.kdyby.org)
5
 *
6
 * Copyright (c) 2008 Filip Procházka ([email protected])
7
 *
8
 * For the full copyright and license information, please view the file license.txt that was distributed with this source code.
9
 */
10
11
namespace Kdyby\Doctrine\DI;
12
13
use Doctrine;
14
use Doctrine\Common\Proxy\AbstractProxyFactory;
15
use Kdyby;
16
use Kdyby\DoctrineCache\DI\Helpers as CacheHelpers;
17
use Nette;
18
use Nette\DI\Statement;
19
use Nette\PhpGenerator as Code;
20
use Nette\PhpGenerator\Method;
21
use Nette\Utils\Strings;
22
use Nette\Utils\Validators;
23
24
25
26
/**
27
 * @author Filip Procházka <[email protected]>
28
 */
29
class OrmExtension extends Nette\DI\CompilerExtension
30
{
31
32
	const ANNOTATION_DRIVER = 'annotations';
33
	const PHP_NAMESPACE = '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff\\\\]*';
34
	const TAG_CONNECTION = 'doctrine.connection';
35
	const TAG_ENTITY_MANAGER = 'doctrine.entityManager';
36
	const TAG_BIND_TO_MANAGER = 'doctrine.bindToManager';
37
	const TAG_REPOSITORY_ENTITY = 'doctrine.repositoryEntity';
38
39
	/**
40
	 * @var array
41
	 */
42
	public $managerDefaults = [
43
		'metadataCache' => 'default',
44
		'queryCache' => 'default',
45
		'resultCache' => 'default',
46
		'hydrationCache' => 'default',
47
		'secondLevelCache' => [
48
			'enabled' => FALSE,
49
			'factoryClass' => 'Doctrine\ORM\Cache\DefaultCacheFactory',
50
			'driver' => 'default',
51
			'regions' => [
52
				'defaultLifetime' => 3600,
53
				'defaultLockLifetime' => 60,
54
			],
55
			'fileLockRegionDirectory' => '%tempDir%/cache/Doctrine.Cache.Locks', // todo fix
56
			'logging' => '%debugMode%',
57
		],
58
		'classMetadataFactory' => 'Kdyby\Doctrine\Mapping\ClassMetadataFactory',
59
		'defaultRepositoryClassName' => 'Kdyby\Doctrine\EntityRepository',
60
		'repositoryFactoryClassName' => 'Kdyby\Doctrine\RepositoryFactory',
61
		'queryBuilderClassName' => 'Kdyby\Doctrine\QueryBuilder',
62
		'autoGenerateProxyClasses' => '%debugMode%',
63
		'namingStrategy' => 'Doctrine\ORM\Mapping\UnderscoreNamingStrategy',
64
		'quoteStrategy' => 'Doctrine\ORM\Mapping\DefaultQuoteStrategy',
65
		'entityListenerResolver' => 'Kdyby\Doctrine\Mapping\EntityListenerResolver',
66
		'proxyDir' => '%tempDir%/proxies',
67
		'proxyNamespace' => 'Kdyby\GeneratedProxy',
68
		'dql' => ['string' => [], 'numeric' => [], 'datetime' => [], 'hints' => []],
69
		'hydrators' => [],
70
		'metadata' => [],
71
		'filters' => [],
72
		'namespaceAlias' => [],
73
		'targetEntityMappings' => [],
74
	];
75
76
	/**
77
	 * @var array
78
	 */
79
	public $connectionDefaults = [
80
		'dbname' => NULL,
81
		'host' => '127.0.0.1',
82
		'port' => NULL,
83
		'user' => NULL,
84
		'password' => NULL,
85
		'charset' => 'UTF8',
86
		'driver' => 'pdo_mysql',
87
		'driverClass' => NULL,
88
		'options' => NULL,
89
		'path' => NULL,
90
		'memory' => NULL,
91
		'unix_socket' => NULL,
92
		'logging' => '%debugMode%',
93
		'platformService' => NULL,
94
		'defaultTableOptions' => [],
95
		'resultCache' => 'default',
96
		'types' => [],
97
		'schemaFilter' => NULL,
98
	];
99
100
	/**
101
	 * @var array
102
	 */
103
	public $metadataDriverClasses = [
104
		self::ANNOTATION_DRIVER => 'Kdyby\Doctrine\Mapping\AnnotationDriver',
105
		'static' => 'Doctrine\Common\Persistence\Mapping\Driver\StaticPHPDriver',
106
		'yml' => 'Doctrine\ORM\Mapping\Driver\YamlDriver',
107
		'yaml' => 'Doctrine\ORM\Mapping\Driver\YamlDriver',
108
		'xml' => 'Doctrine\ORM\Mapping\Driver\XmlDriver',
109
		'db' => 'Doctrine\ORM\Mapping\Driver\DatabaseDriver',
110
	];
111
112
	/**
113
	 * @var array
114
	 */
115
	private $proxyAutoloaders = [];
116
117
	/**
118
	 * @var array
119
	 */
120
	private $targetEntityMappings = [];
121
122
	/**
123
	 * @var array
124
	 */
125
	private $configuredManagers = [];
126
127
	/**
128
	 * @var array
129
	 */
130
	private $managerConfigs = [];
131
132
	/**
133
	 * @var array
134
	 */
135
	private $configuredConnections = [];
136
137
	/**
138
	 * @var array
139
	 */
140
	private $postCompileRepositoriesQueue = [];
141
142
143
144
	public function loadConfiguration()
145
	{
146
		$this->proxyAutoloaders =
147
		$this->targetEntityMappings =
148
		$this->configuredConnections =
149
		$this->managerConfigs =
150
		$this->configuredManagers =
151
		$this->postCompileRepositoriesQueue = [];
152
153
		if (!$this->compiler->getExtensions('Kdyby\Annotations\DI\AnnotationsExtension')) {
154
			throw new Nette\Utils\AssertionException('You should register \'Kdyby\Annotations\DI\AnnotationsExtension\' before \'' . get_class($this) . '\'.', E_USER_NOTICE);
155
		}
156
157
		$builder = $this->getContainerBuilder();
158
		$config = $this->getConfig();
159
160
		$builder->parameters[$this->prefix('debug')] = !empty($config['debug']);
161
		if (isset($config['dbname']) || isset($config['driver']) || isset($config['connection'])) {
162
			$config = ['default' => $config];
163
			$defaults = ['debug' => $builder->parameters['debugMode']];
164
165
		} else {
166
			$defaults = array_intersect_key($config, $this->managerDefaults)
167
				+ array_intersect_key($config, $this->connectionDefaults)
168
				+ ['debug' => $builder->parameters['debugMode']];
169
170
			$config = array_diff_key($config, $defaults);
171
		}
172
173
		if (empty($config)) {
174
			throw new Kdyby\Doctrine\UnexpectedValueException("Please configure the Doctrine extensions using the section '{$this->name}:' in your config file.");
175
		}
176
177
		foreach ($config as $name => $emConfig) {
178
			if (!is_array($emConfig) || (empty($emConfig['dbname']) && empty($emConfig['driver']))) {
179
				throw new Kdyby\Doctrine\UnexpectedValueException("Please configure the Doctrine extensions using the section '{$this->name}:' in your config file.");
180
			}
181
182
			$emConfig = Nette\DI\Config\Helpers::merge($emConfig, $defaults);
183
			$this->processEntityManager($name, $emConfig);
184
		}
185
186
		if ($this->targetEntityMappings) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->targetEntityMappings of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
187
			if (!$this->isKdybyEventsPresent()) {
188
				throw new Nette\Utils\AssertionException('The option \'targetEntityMappings\' requires \'Kdyby\Events\DI\EventsExtension\'.', E_USER_NOTICE);
189
			}
190
191
			$listener = $builder->addDefinition($this->prefix('resolveTargetEntityListener'))
192
				->setClass('Kdyby\Doctrine\Tools\ResolveTargetEntityListener')
193
				->addTag(Kdyby\Events\DI\EventsExtension::SUBSCRIBER_TAG);
194
195
			foreach ($this->targetEntityMappings as $originalEntity => $mapping) {
196
				$listener->addSetup('addResolveTargetEntity', [$originalEntity, $mapping['targetEntity'], $mapping]);
197
			}
198
		}
199
200
		$this->loadConsole();
201
202
		$builder->addDefinition($this->prefix('registry'))
203
			->setClass('Kdyby\Doctrine\Registry', [
204
				$this->configuredConnections,
205
				$this->configuredManagers,
206
				$builder->parameters[$this->name]['dbal']['defaultConnection'],
207
				$builder->parameters[$this->name]['orm']['defaultEntityManager'],
208
			]);
209
	}
210
211
212
213
	protected function loadConsole()
214
	{
215
		$builder = $this->getContainerBuilder();
216
217
		foreach ($this->loadFromFile(__DIR__ . '/console.neon') as $i => $command) {
218
			$cli = $builder->addDefinition($this->prefix('cli.' . $i))
219
				->addTag(Kdyby\Console\DI\ConsoleExtension::COMMAND_TAG)
220
				->addTag(Nette\DI\Extensions\InjectExtension::TAG_INJECT, FALSE); // lazy injects
221
222
			if (is_string($command)) {
223
				$cli->setClass($command);
224
225
			} else {
226
				throw new Kdyby\Doctrine\NotSupportedException;
227
			}
228
		}
229
	}
230
231
232
233
	protected function processEntityManager($name, array $defaults)
234
	{
235
		$builder = $this->getContainerBuilder();
236
		$config = $this->resolveConfig($defaults, $this->managerDefaults, $this->connectionDefaults);
237
238
		if ($isDefault = !isset($builder->parameters[$this->name]['orm']['defaultEntityManager'])) {
239
			$builder->parameters[$this->name]['orm']['defaultEntityManager'] = $name;
240
		}
241
242
		$metadataDriver = $builder->addDefinition($this->prefix($name . '.metadataDriver'))
243
			->setClass('Doctrine\Common\Persistence\Mapping\Driver\MappingDriverChain')
244
			->setAutowired(FALSE);
245
		/** @var Nette\DI\ServiceDefinition $metadataDriver */
246
247
		Validators::assertField($config, 'metadata', 'array');
248
		Validators::assertField($config, 'targetEntityMappings', 'array');
249
		$config['targetEntityMappings'] = $this->normalizeTargetEntityMappings($config['targetEntityMappings']);
250
		foreach ($this->compiler->getExtensions() as $extension) {
251
			if ($extension instanceof IEntityProvider) {
252
				$metadata = $extension->getEntityMappings();
253
				Validators::assert($metadata, 'array');
254
				$config['metadata'] = array_merge($config['metadata'], $metadata);
255
			}
256
257
			if ($extension instanceof ITargetEntityProvider) {
258
				$targetEntities = $extension->getTargetEntityMappings();
259
				Validators::assert($targetEntities, 'array');
260
				$config['targetEntityMappings'] = Nette\Utils\Arrays::mergeTree($config['targetEntityMappings'], $this->normalizeTargetEntityMappings($targetEntities));
261
			}
262
263
			if ($extension instanceof IDatabaseTypeProvider) {
264
				$providedTypes = $extension->getDatabaseTypes();
265
				Validators::assert($providedTypes, 'array');
266
267
				if (!isset($defaults['types'])) {
268
					$defaults['types'] = [];
269
				}
270
271
				$defaults['types'] = array_merge($defaults['types'], $providedTypes);
272
			}
273
		}
274
275
		foreach (self::natSortKeys($config['metadata']) as $namespace => $driver) {
276
			$this->processMetadataDriver($metadataDriver, $namespace, $driver, $name);
277
		}
278
279
		$this->processMetadataDriver($metadataDriver, 'Kdyby\\Doctrine', __DIR__ . '/../Entities', $name);
280
281
		if (empty($config['metadata'])) {
282
			$metadataDriver->addSetup('setDefaultDriver', [
283
				new Statement($this->metadataDriverClasses[self::ANNOTATION_DRIVER], [
284
					[$builder->expand('%appDir%')],
285
					2 => $this->prefix('@cache.' . $name . '.metadata')
286
				])
287
			]);
288
		}
289
290
		if ($config['repositoryFactoryClassName'] === 'default') {
291
			$config['repositoryFactoryClassName'] = 'Doctrine\ORM\Repository\DefaultRepositoryFactory';
292
		}
293
		$builder->addDefinition($this->prefix($name . '.repositoryFactory'))
294
			->setClass($config['repositoryFactoryClassName'])
295
			->setAutowired(FALSE);
296
297
		Validators::assertField($config, 'namespaceAlias', 'array');
298
		Validators::assertField($config, 'hydrators', 'array');
299
		Validators::assertField($config, 'dql', 'array');
300
		Validators::assertField($config['dql'], 'string', 'array');
301
		Validators::assertField($config['dql'], 'numeric', 'array');
302
		Validators::assertField($config['dql'], 'datetime', 'array');
303
		Validators::assertField($config['dql'], 'hints', 'array');
304
305
		$autoGenerateProxyClasses = is_bool($config['autoGenerateProxyClasses'])
306
			? ($config['autoGenerateProxyClasses'] ? AbstractProxyFactory::AUTOGENERATE_ALWAYS : AbstractProxyFactory::AUTOGENERATE_NEVER)
307
			: $config['autoGenerateProxyClasses'];
308
309
		$configuration = $builder->addDefinition($this->prefix($name . '.ormConfiguration'))
310
			->setClass('Kdyby\Doctrine\Configuration')
311
			->addSetup('setMetadataCacheImpl', [$this->processCache($config['metadataCache'], $name . '.metadata')])
312
			->addSetup('setQueryCacheImpl', [$this->processCache($config['queryCache'], $name . '.query')])
313
			->addSetup('setResultCacheImpl', [$this->processCache($config['resultCache'], $name . '.ormResult')])
314
			->addSetup('setHydrationCacheImpl', [$this->processCache($config['hydrationCache'], $name . '.hydration')])
315
			->addSetup('setMetadataDriverImpl', [$this->prefix('@' . $name . '.metadataDriver')])
316
			->addSetup('setClassMetadataFactoryName', [$config['classMetadataFactory']])
317
			->addSetup('setDefaultRepositoryClassName', [$config['defaultRepositoryClassName']])
318
			->addSetup('setQueryBuilderClassName', [$config['queryBuilderClassName']])
319
			->addSetup('setRepositoryFactory', [$this->prefix('@' . $name . '.repositoryFactory')])
320
			->addSetup('setProxyDir', [$config['proxyDir']])
321
			->addSetup('setProxyNamespace', [$config['proxyNamespace']])
322
			->addSetup('setAutoGenerateProxyClasses', [$autoGenerateProxyClasses])
323
			->addSetup('setEntityNamespaces', [$config['namespaceAlias']])
324
			->addSetup('setCustomHydrationModes', [$config['hydrators']])
325
			->addSetup('setCustomStringFunctions', [$config['dql']['string']])
326
			->addSetup('setCustomNumericFunctions', [$config['dql']['numeric']])
327
			->addSetup('setCustomDatetimeFunctions', [$config['dql']['datetime']])
328
			->addSetup('setDefaultQueryHints', [$config['dql']['hints']])
329
			->addSetup('setNamingStrategy', CacheHelpers::filterArgs($config['namingStrategy']))
330
			->addSetup('setQuoteStrategy', CacheHelpers::filterArgs($config['quoteStrategy']))
331
			->addSetup('setEntityListenerResolver', CacheHelpers::filterArgs($config['entityListenerResolver']))
332
			->setAutowired(FALSE);
333
		/** @var Nette\DI\ServiceDefinition $configuration */
334
335
		$this->proxyAutoloaders[$config['proxyNamespace']] = $config['proxyDir'];
336
337
		$this->processSecondLevelCache($name, $config['secondLevelCache'], $isDefault);
338
339
		Validators::assertField($config, 'filters', 'array');
340
		foreach ($config['filters'] as $filterName => $filterClass) {
341
			$configuration->addSetup('addFilter', [$filterName, $filterClass]);
342
		}
343
344
		if ($config['targetEntityMappings']) {
345
			$configuration->addSetup('setTargetEntityMap', [array_map(function ($mapping) {
346
				return $mapping['targetEntity'];
347
			}, $config['targetEntityMappings'])]);
348
			$this->targetEntityMappings = Nette\Utils\Arrays::mergeTree($this->targetEntityMappings, $config['targetEntityMappings']);
349
		}
350
351
		if ($this->isKdybyEventsPresent()) {
352
			$builder->addDefinition($this->prefix($name . '.evm'))
353
				->setClass('Kdyby\Events\NamespacedEventManager', [Kdyby\Doctrine\Events::NS . '::'])
354
				->addSetup('$dispatchGlobalEvents', [TRUE]) // for BC
355
				->setAutowired(FALSE);
356
357
		} else {
358
			$builder->addDefinition($this->prefix($name . '.evm'))
359
				->setClass('Doctrine\Common\EventManager')
360
				->setAutowired(FALSE);
361
		}
362
363
		// entity manager
364
		$entityManager = $builder->addDefinition($managerServiceId = $this->prefix($name . '.entityManager'))
365
			->setClass('Kdyby\Doctrine\EntityManager')
366
			->setFactory('Kdyby\Doctrine\EntityManager::create', [
367
				$connectionService = $this->processConnection($name, $defaults, $isDefault),
368
				$this->prefix('@' . $name . '.ormConfiguration'),
369
				$this->prefix('@' . $name . '.evm'),
370
			])
371
			->addTag(self::TAG_ENTITY_MANAGER)
372
			->addTag('kdyby.doctrine.entityManager')
373
			->setAutowired($isDefault);
374
375
		if ($this->isTracyPresent()) {
376
			$entityManager->addSetup('?->bindEntityManager(?)', [$this->prefix('@' . $name . '.diagnosticsPanel'), '@self']);
377
		}
378
379
		$builder->addDefinition($this->prefix('repositoryFactory.' . $name . '.defaultRepositoryFactory'))
380
				->setClass($config['defaultRepositoryClassName'])
381
				->setImplement('Kdyby\Doctrine\DI\IRepositoryFactory')
382
				->setArguments([new Code\PhpLiteral('$entityManager'), new Code\PhpLiteral('$classMetadata')])
383
				->setParameters(['Doctrine\ORM\EntityManagerInterface entityManager', 'Doctrine\ORM\Mapping\ClassMetadata classMetadata'])
384
				->setAutowired(FALSE);
385
386
		$builder->addDefinition($this->prefix($name . '.schemaValidator'))
387
			->setClass('Doctrine\ORM\Tools\SchemaValidator', ['@' . $managerServiceId])
388
			->setAutowired($isDefault);
389
390
		$builder->addDefinition($this->prefix($name . '.schemaTool'))
391
			->setClass('Doctrine\ORM\Tools\SchemaTool', ['@' . $managerServiceId])
392
			->setAutowired($isDefault);
393
394
		$cacheCleaner = $builder->addDefinition($this->prefix($name . '.cacheCleaner'))
395
			->setClass('Kdyby\Doctrine\Tools\CacheCleaner', ['@' . $managerServiceId])
396
			->setAutowired($isDefault);
397
398
		$builder->addDefinition($this->prefix($name . '.schemaManager'))
399
			->setClass('Doctrine\DBAL\Schema\AbstractSchemaManager')
400
			->setFactory('@Kdyby\Doctrine\Connection::getSchemaManager')
401
			->setAutowired($isDefault);
402
403
		foreach ($this->compiler->getExtensions('Kdyby\Annotations\DI\AnnotationsExtension') as $extension) {
404
			/** @var Kdyby\Annotations\DI\AnnotationsExtension $extension */
405
			$cacheCleaner->addSetup('addCacheStorage', [$extension->prefix('@cache.annotations')]);
406
		}
407
408
		if ($isDefault) {
409
			$builder->addDefinition($this->prefix('helper.entityManager'))
410
				->setClass('Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper', ['@' . $managerServiceId])
411
				->addTag(Kdyby\Console\DI\ConsoleExtension::HELPER_TAG, 'em');
412
413
			$builder->addDefinition($this->prefix('helper.connection'))
414
				->setClass('Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper', [$connectionService])
415
				->addTag(Kdyby\Console\DI\ConsoleExtension::HELPER_TAG, 'db');
416
417
			$builder->addAlias($this->prefix('schemaValidator'), $this->prefix($name . '.schemaValidator'));
418
			$builder->addAlias($this->prefix('schemaTool'), $this->prefix($name . '.schemaTool'));
419
			$builder->addAlias($this->prefix('cacheCleaner'), $this->prefix($name . '.cacheCleaner'));
420
			$builder->addAlias($this->prefix('schemaManager'), $this->prefix($name . '.schemaManager'));
421
		}
422
423
		$this->configuredManagers[$name] = $managerServiceId;
424
		$this->managerConfigs[$name] = $config;
425
	}
426
427
428
429
	protected function processSecondLevelCache($name, array $config, $isDefault)
430
	{
431
		if (!$config['enabled']) {
432
			return;
433
		}
434
435
		$builder = $this->getContainerBuilder();
436
437
		$cacheFactory = $builder->addDefinition($this->prefix($name . '.cacheFactory'))
438
			->setClass('Doctrine\ORM\Cache\CacheFactory')
439
			->setFactory($config['factoryClass'], [
440
				$this->prefix('@' . $name . '.cacheRegionsConfiguration'),
441
				$this->processCache($config['driver'], $name . '.secondLevel'),
442
			])
443
			->setAutowired($isDefault);
444
445
		if ($config['factoryClass'] === $this->managerDefaults['secondLevelCache']['factoryClass']
446
			|| is_subclass_of($config['factoryClass'], $this->managerDefaults['secondLevelCache']['factoryClass'])
447
		) {
448
			$cacheFactory->addSetup('setFileLockRegionDirectory', [$config['fileLockRegionDirectory']]);
449
		}
450
451
		$builder->addDefinition($this->prefix($name . '.cacheRegionsConfiguration'))
452
			->setClass('Doctrine\ORM\Cache\RegionsConfiguration', [
453
				$config['regions']['defaultLifetime'],
454
				$config['regions']['defaultLockLifetime'],
455
			])
456
			->setAutowired($isDefault);
457
458
		$logger = $builder->addDefinition($this->prefix($name . '.cacheLogger'))
459
			->setClass('Doctrine\ORM\Cache\Logging\CacheLogger')
460
			->setFactory('Doctrine\ORM\Cache\Logging\CacheLoggerChain')
461
			->setAutowired(FALSE);
462
463
		if ($config['logging']) {
464
			$logger->addSetup('setLogger', [
465
				'statistics',
466
				new Statement('Doctrine\ORM\Cache\Logging\StatisticsCacheLogger')
467
			]);
468
		}
469
470
		$builder->addDefinition($cacheConfigName = $this->prefix($name . '.ormCacheConfiguration'))
471
			->setClass('Doctrine\ORM\Cache\CacheConfiguration')
472
			->addSetup('setCacheFactory', [$this->prefix('@' . $name . '.cacheFactory')])
473
			->addSetup('setCacheLogger', [$this->prefix('@' . $name . '.cacheLogger')])
474
			->setAutowired($isDefault);
475
476
		$configuration = $builder->getDefinition($this->prefix($name . '.ormConfiguration'));
477
		$configuration->addSetup('setSecondLevelCacheEnabled');
478
		$configuration->addSetup('setSecondLevelCacheConfiguration', ['@' . $cacheConfigName]);
479
	}
480
481
482
483
	protected function processConnection($name, array $defaults, $isDefault = FALSE)
484
	{
485
		$builder = $this->getContainerBuilder();
486
		$config = $this->resolveConfig($defaults, $this->connectionDefaults, $this->managerDefaults);
487
488
		if ($isDefault) {
489
			$builder->parameters[$this->name]['dbal']['defaultConnection'] = $name;
490
		}
491
492
		if (isset($defaults['connection'])) {
493
			return $this->prefix('@' . $defaults['connection'] . '.connection');
494
		}
495
496
		// config
497
		$configuration = $builder->addDefinition($this->prefix($name . '.dbalConfiguration'))
498
			->setClass('Doctrine\DBAL\Configuration')
499
			->addSetup('setResultCacheImpl', [$this->processCache($config['resultCache'], $name . '.dbalResult')])
500
			->addSetup('setSQLLogger', [new Statement('Doctrine\DBAL\Logging\LoggerChain')])
501
			->addSetup('setFilterSchemaAssetsExpression', [$config['schemaFilter']])
502
			->setAutowired(FALSE);
503
504
		// types
505
		Validators::assertField($config, 'types', 'array');
506
		$schemaTypes = $dbalTypes = [];
507
		foreach ($config['types'] as $dbType => $className) {
508
			$typeInst = Code\Helpers::createObject($className, []);
509
			/** @var Doctrine\DBAL\Types\Type $typeInst */
510
			$dbalTypes[$typeInst->getName()] = $className;
511
			$schemaTypes[$dbType] = $typeInst->getName();
512
		}
513
514
		// tracy panel
515
		if ($this->isTracyPresent()) {
516
			$builder->addDefinition($this->prefix($name . '.diagnosticsPanel'))
517
				->setClass('Kdyby\Doctrine\Diagnostics\Panel')
518
				->setAutowired(FALSE);
519
		}
520
521
		// connection
522
		$options = array_diff_key($config, array_flip(['types', 'resultCache', 'connection', 'logging']));
523
		$connection = $builder->addDefinition($connectionServiceId = $this->prefix($name . '.connection'))
524
			->setClass('Kdyby\Doctrine\Connection')
525
			->setFactory('Kdyby\Doctrine\Connection::create', [
526
				$options,
527
				$this->prefix('@' . $name . '.dbalConfiguration'),
528
				$this->prefix('@' . $name . '.evm'),
529
			])
530
			->addSetup('setSchemaTypes', [$schemaTypes])
531
			->addSetup('setDbalTypes', [$dbalTypes])
532
			->addTag(self::TAG_CONNECTION)
533
			->addTag('kdyby.doctrine.connection')
534
			->setAutowired($isDefault);
535
536
		if ($this->isTracyPresent()) {
537
			$connection->addSetup('$panel = ?->bindConnection(?)', [$this->prefix('@' . $name . '.diagnosticsPanel'), '@self']);
538
		}
539
540
		/** @var Nette\DI\ServiceDefinition $connection */
541
542
		$this->configuredConnections[$name] = $connectionServiceId;
543
544
		if (!is_bool($config['logging'])) {
545
			$fileLogger = new Statement('Kdyby\Doctrine\Diagnostics\FileLogger', [$builder->expand($config['logging'])]);
546
			$configuration->addSetup('$service->getSQLLogger()->addLogger(?)', [$fileLogger]);
547
548
		} elseif ($config['logging']) {
549
			$connection->addSetup('?->enableLogging()', [new Code\PhpLiteral('$panel')]);
550
		}
551
552
		return $this->prefix('@' . $name . '.connection');
553
	}
554
555
556
557
	/**
558
	 * @param \Nette\DI\ServiceDefinition $metadataDriver
559
	 * @param string $namespace
560
	 * @param string|object $driver
561
	 * @param string $prefix
562
	 * @throws \Nette\Utils\AssertionException
563
	 * @return string
564
	 */
565
	protected function processMetadataDriver(Nette\DI\ServiceDefinition $metadataDriver, $namespace, $driver, $prefix)
566
	{
567
		if (!is_string($namespace) || !Strings::match($namespace, '#^' . self::PHP_NAMESPACE . '\z#')) {
568
			throw new Nette\Utils\AssertionException("The metadata namespace expects to be valid namespace, $namespace given.");
569
		}
570
		$namespace = ltrim($namespace, '\\');
571
572
		if (is_string($driver) || is_array($driver)) {
573
			$paths = is_array($driver) ? $driver : [$driver];
574
			foreach ($paths as $path) {
575
				if (($pos = strrpos($path, '*')) !== FALSE) {
576
					$path = substr($path, 0, $pos);
577
				}
578
579
				if (!file_exists($path)) {
580
					throw new Nette\Utils\AssertionException("The metadata path expects to be an existing directory, $path given.");
581
				}
582
			}
583
584
			$driver = new Statement(self::ANNOTATION_DRIVER, is_array($paths) ? $paths : [$paths]);
585
		}
586
587
		$impl = $driver instanceof \stdClass ? $driver->value : ($driver instanceof Statement ? $driver->getEntity() : (string) $driver);
0 ignored issues
show
Bug introduced by
The class Nette\DI\Statement does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
588
		list($driver) = CacheHelpers::filterArgs($driver);
589
		/** @var Statement $driver */
590
591
		/** @var string $impl */
592
		if (isset($this->metadataDriverClasses[$impl])) {
593
			$driver = new Statement($this->metadataDriverClasses[$impl], $driver->arguments);
594
		}
595
596
		if (is_string($driver->getEntity()) && substr($driver->getEntity(), 0, 1) === '@') {
597
			$metadataDriver->addSetup('addDriver', [$driver->getEntity(), $namespace]);
598
			return $driver->getEntity();
599
		}
600
601
		if ($impl === self::ANNOTATION_DRIVER) {
602
			$driver->arguments = [
603
				Nette\Utils\Arrays::flatten($driver->arguments),
604
				2 => $this->prefix('@cache.' . $prefix . '.metadata')
605
			];
606
		}
607
608
		$serviceName = $this->prefix($prefix . '.driver.' . str_replace('\\', '_', $namespace) . '.' . str_replace('\\', '_', $impl) . 'Impl');
609
610
		$this->getContainerBuilder()->addDefinition($serviceName)
611
			->setClass('Doctrine\Common\Persistence\Mapping\Driver\MappingDriver')
612
			->setFactory($driver->getEntity(), $driver->arguments)
613
			->setAutowired(FALSE);
614
615
		$metadataDriver->addSetup('addDriver', ['@' . $serviceName, $namespace]);
616
		return '@' . $serviceName;
617
	}
618
619
620
621
	/**
622
	 * @param string|\stdClass $cache
623
	 * @param string $suffix
624
	 * @return string
625
	 */
626
	protected function processCache($cache, $suffix)
627
	{
628
		return CacheHelpers::processCache($this, $cache, $suffix, $this->getContainerBuilder()->parameters[$this->prefix('debug')]);
629
	}
630
631
632
633
	public function beforeCompile()
634
	{
635
		$this->processRepositories();
636
		$this->processEventManagers();
637
	}
638
639
640
641
	protected function processRepositories()
642
	{
643
		$builder = $this->getContainerBuilder();
644
645
		$disabled = TRUE;
646
		foreach ($this->configuredManagers as $managerName => $_) {
647
			$factoryClassName = $builder->getDefinition($this->prefix($managerName . '.repositoryFactory'))->getClass();
648
			if ($factoryClassName === 'Kdyby\Doctrine\RepositoryFactory' || in_array('Kdyby\Doctrine\RepositoryFactory', class_parents($factoryClassName), TRUE)) {
649
				$disabled = FALSE;
650
			}
651
		}
652
653
		if ($disabled) {
654
			return;
655
		}
656
657
		if (!method_exists($builder, 'findByType')) {
658
			foreach ($this->configuredManagers as $managerName => $_) {
659
				$builder->getDefinition($this->prefix($managerName . '.repositoryFactory'))
660
					->addSetup('setServiceIdsMap', [[], $this->prefix('repositoryFactory.' . $managerName . '.defaultRepositoryFactory')]);
661
			}
662
663
			return;
664
		}
665
666
		$serviceMap = array_fill_keys(array_keys($this->configuredManagers), []);
667
		foreach ($builder->findByType('Doctrine\ORM\EntityRepository', FALSE) as $originalServiceName => $originalDef) {
668
			if (is_string($originalDef)) {
669
				$originalServiceName = $originalDef;
670
				$originalDef = $builder->getDefinition($originalServiceName);
671
			}
672
673
			if (strpos($originalServiceName, $this->name . '.') === 0) {
674
				continue; // ignore
675
			}
676
677
			$factory = $originalDef->getFactory() ? $originalDef->getFactory()->getEntity() : $originalDef->getClass();
678
			if (stripos($factory, '::getRepository') !== FALSE) {
679
				continue; // ignore
680
			}
681
682
			$factoryServiceName = $this->prefix('repositoryFactory.' . $originalServiceName);
683
			$factoryDef = $builder->addDefinition($factoryServiceName, $originalDef)
684
				->setImplement('Kdyby\Doctrine\DI\IRepositoryFactory')
685
				->setParameters(['Doctrine\ORM\EntityManagerInterface entityManager', 'Doctrine\ORM\Mapping\ClassMetadata classMetadata'])
686
				->setAutowired(FALSE);
687
			$factoryStatement = $factoryDef->getFactory() ?: new Statement($factoryDef->getClass());
688
			$factoryStatement->arguments[0] = new Code\PhpLiteral('$entityManager');
689
			$factoryStatement->arguments[1] = new Code\PhpLiteral('$classMetadata');
690
			$factoryDef->setArguments($factoryStatement->arguments);
691
692
			$boundManagers = $this->getServiceBoundManagers($originalDef);
693
			Validators::assert($boundManagers, 'list:1', 'bound manager');
694
695
			if ($boundEntity = $originalDef->getTag(self::TAG_REPOSITORY_ENTITY)) {
696
				if (!is_string($boundEntity) || !class_exists($boundEntity)) {
697
					throw new Nette\Utils\AssertionException(sprintf('The entity "%s" for repository "%s" cannot be autoloaded.', $boundEntity, $originalDef->getClass()));
698
				}
699
				$entityArgument = $boundEntity;
700
701
			} else {
702
				$entityArgument = new Code\PhpLiteral('"%entityName%"');
703
				$this->postCompileRepositoriesQueue[$boundManagers[0]][] = [ltrim($originalDef->getClass(), '\\'), $originalServiceName];
704
			}
705
706
			$builder->removeDefinition($originalServiceName);
707
			$builder->addDefinition($originalServiceName)
708
				->setClass($originalDef->getClass())
709
				->setFactory(sprintf('@%s::getRepository', $this->configuredManagers[$boundManagers[0]]), [$entityArgument]);
710
711
			$serviceMap[$boundManagers[0]][$originalDef->getClass()] = $factoryServiceName;
712
		}
713
714
		foreach ($this->configuredManagers as $managerName => $_) {
715
			$builder->getDefinition($this->prefix($managerName . '.repositoryFactory'))
716
				->addSetup('setServiceIdsMap', [
717
					$serviceMap[$managerName],
718
					$this->prefix('repositoryFactory.' . $managerName . '.defaultRepositoryFactory')
719
				]);
720
		}
721
	}
722
723
724
725
	protected function processEventManagers()
726
	{
727
		$builder = $this->getContainerBuilder();
728
		$customEvmService = $builder->getByType('Doctrine\Common\EventManager');
729
		if ($this->isKdybyEventsPresent() || !$customEvmService) {
730
			return;
731
		}
732
733
		foreach ($this->configuredManagers as $managerName => $_) {
734
			$builder->getDefinition($this->prefix($managerName . '.evm'))->setFactory('@' . $customEvmService);
735
		}
736
	}
737
738
739
740
	/**
741
	 * @param Nette\DI\ServiceDefinition $def
742
	 * @return string[]
743
	 */
744
	protected function getServiceBoundManagers(Nette\DI\ServiceDefinition $def)
745
	{
746
		$builder = $this->getContainerBuilder();
747
		$boundManagers = $def->getTag(self::TAG_BIND_TO_MANAGER);
748
749
		return is_array($boundManagers) ? $boundManagers : [$builder->parameters[$this->name]['orm']['defaultEntityManager']];
750
	}
751
752
753
754
	public function afterCompile(Code\ClassType $class)
755
	{
756
		$init = $class->getMethod('initialize');
757
758
		if ($this->isTracyPresent()) {
759
			$init->addBody('Kdyby\Doctrine\Diagnostics\Panel::registerBluescreen($this);');
760
			$this->addCollapsePathsToTracy($init);
761
		}
762
763
		foreach ($this->proxyAutoloaders as $namespace => $dir) {
764
			$originalInitialize = $init->getBody();
765
			$init->setBody('Kdyby\Doctrine\Proxy\ProxyAutoloader::create(?, ?)->register();', [$dir, $namespace]);
766
			$init->addBody($originalInitialize);
767
		}
768
769
		$this->processRepositoryFactoryEntities($class);
770
	}
771
772
773
774
	protected function processRepositoryFactoryEntities(Code\ClassType $class)
775
	{
776
		if (empty($this->postCompileRepositoriesQueue)) {
777
			return;
778
		}
779
780
		$dic = self::evalAndInstantiateContainer($class);
781
782
		foreach ($this->postCompileRepositoriesQueue as $manager => $items) {
783
			$config = $this->managerConfigs[$manager];
784
			/** @var Kdyby\Doctrine\EntityManager $entityManager */
785
			$entityManager = $dic->getService($this->configuredManagers[$manager]);
786
			/** @var Doctrine\ORM\Mapping\ClassMetadata $entityMetadata */
787
			$metadataFactory = $entityManager->getMetadataFactory();
788
789
			$allMetadata = [];
790
			foreach ($metadataFactory->getAllMetadata() as $entityMetadata) {
791
				if ($config['defaultRepositoryClassName'] === $entityMetadata->customRepositoryClassName || empty($entityMetadata->customRepositoryClassName)) {
792
					continue;
793
				}
794
795
				$allMetadata[ltrim($entityMetadata->customRepositoryClassName, '\\')] = $entityMetadata;
796
			}
797
798
			foreach ($items as $item) {
799
				if (!isset($allMetadata[$item[0]])) {
800
					throw new Nette\Utils\AssertionException(sprintf('Repository class %s have been found in DIC, but no entity has it assigned and it has no entity configured', $item[0]));
801
				}
802
803
				$entityMetadata = $allMetadata[$item[0]];
804
				$serviceMethod = Nette\DI\Container::getMethodName($item[1]);
805
806
				$method = $class->getMethod($serviceMethod);
807
				$methodBody = $method->getBody();
808
				$method->setBody(str_replace('"%entityName%"', Code\Helpers::format('?', $entityMetadata->getName()), $methodBody));
809
			}
810
		}
811
	}
812
813
814
815
	/**
816
	 * @param Code\ClassType $class
817
	 * @return Nette\DI\Container
818
	 */
819
	private static function evalAndInstantiateContainer(Code\ClassType $class)
820
	{
821
		$classCopy = clone $class;
822
		$classCopy->setName($className = 'Kdyby_Doctrine_IamTheKingOfHackingNette_' . $class->getName() . '_' . rand());
823
824
		$containerCode = "$classCopy";
825
826
		return call_user_func(function () use ($className, $containerCode) {
827
			eval($containerCode);
0 ignored issues
show
Coding Style introduced by
It is generally not recommended to use eval unless absolutely required.

On one hand, eval might be exploited by malicious users if they somehow manage to inject dynamic content. On the other hand, with the emergence of faster PHP runtimes like the HHVM, eval prevents some optimization that they perform.

Loading history...
828
			return new $className();
829
		});
830
	}
831
832
833
834
	/**
835
	 * @param $provided
836
	 * @param $defaults
837
	 * @param $diff
838
	 * @return array
839
	 */
840
	private function resolveConfig(array $provided, array $defaults, array $diff = [])
841
	{
842
		return $this->getContainerBuilder()->expand(Nette\DI\Config\Helpers::merge(
843
			array_diff_key($provided, array_diff_key($diff, $defaults)),
844
			$defaults
845
		));
846
	}
847
848
849
	/**
850
	 * @param array $targetEntityMappings
851
	 * @return array
852
	 */
853
	private function normalizeTargetEntityMappings(array $targetEntityMappings)
854
	{
855
		$normalized = [];
856
		foreach ($targetEntityMappings as $originalEntity => $targetEntity) {
857
			$originalEntity = ltrim($originalEntity, '\\');
858
			Validators::assert($targetEntity, 'array|string');
859
			if (is_array($targetEntity)) {
860
				Validators::assertField($targetEntity, 'targetEntity', 'string');
861
				$mapping = array_merge($targetEntity, [
862
					'targetEntity' => ltrim($targetEntity['targetEntity'], '\\')
863
				]);
864
865
			} else {
866
				$mapping = [
867
					'targetEntity' => ltrim($targetEntity, '\\'),
868
				];
869
			}
870
			$normalized[$originalEntity] = $mapping;
871
		}
872
		return $normalized;
873
	}
874
875
876
877
	/**
878
	 * @return bool
879
	 */
880
	private function isTracyPresent()
881
	{
882
		return interface_exists('Tracy\IBarPanel');
883
	}
884
885
886
887
	/**
888
	 * @return bool
889
	 */
890
	private function isKdybyEventsPresent()
891
	{
892
		return (bool) $this->compiler->getExtensions('Kdyby\Events\DI\EventsExtension');
893
	}
894
895
896
897
	private function addCollapsePathsToTracy(Method $init)
898
	{
899
		$blueScreen = 'Tracy\Debugger::getBlueScreen()';
900
		$commonDirname = dirname(Nette\Reflection\ClassType::from('Doctrine\Common\Version')->getFileName());
901
902
		$init->addBody($blueScreen . '->collapsePaths[] = ?;', [dirname(Nette\Reflection\ClassType::from('Kdyby\Doctrine\Exception')->getFileName())]);
903
		$init->addBody($blueScreen . '->collapsePaths[] = ?;', [dirname(dirname(dirname(dirname($commonDirname))))]); // this should be vendor/doctrine
904
		foreach ($this->proxyAutoloaders as $dir) {
905
			$init->addBody($blueScreen . '->collapsePaths[] = ?;', [$dir]);
906
		}
907
	}
908
909
910
911
	/**
912
	 * @param \Nette\Configurator $configurator
913
	 */
914
	public static function register(Nette\Configurator $configurator)
915
	{
916
		$configurator->onCompile[] = function ($config, Nette\DI\Compiler $compiler) {
917
			$compiler->addExtension('doctrine', new OrmExtension());
918
		};
919
	}
920
921
922
923
	/**
924
	 * @param array $array
925
	 */
926
	private static function natSortKeys(array &$array)
927
	{
928
		$keys = array_keys($array);
929
		natsort($keys);
930
		$keys = array_flip(array_reverse($keys, TRUE));
931
		$array = array_merge($keys, $array);
932
		return $array;
933
	}
934
935
}
936