Completed
Push — master ( 26da79...8decc6 )
by Jáchym
06:04 queued 04:34
created

OrmExtension::processMetadataDriver()   C

Complexity

Conditions 17
Paths 75

Size

Total Lines 54
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 54
rs 6.6619
cc 17
eloc 31
nc 75
nop 4

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
/**
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 Doctrine\ORM\EntityManagerInterface;
16
use Doctrine\ORM\Repository\DefaultRepositoryFactory;
17
use Kdyby;
18
use Kdyby\DoctrineCache\DI\Helpers as CacheHelpers;
19
use Nette;
20
use Nette\DI\Statement;
21
use Nette\PhpGenerator as Code;
22
use Nette\PhpGenerator\Method;
23
use Nette\Utils\Strings;
24
use Nette\Utils\Validators;
25
use Doctrine\DBAL\Schema\AbstractSchemaManager;
26
use Kdyby\Annotations\DI\AnnotationsExtension;
27
use Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper;
28
use Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper;
29
30
/**
31
 * @author Filip Procházka <[email protected]>
32
 */
33
class OrmExtension extends Nette\DI\CompilerExtension
34
{
35
36
	const ANNOTATION_DRIVER = 'annotations';
37
	const PHP_NAMESPACE = '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff\\\\]*';
38
	const TAG_CONNECTION = 'doctrine.connection';
39
	const TAG_ENTITY_MANAGER = 'doctrine.entityManager';
40
	const TAG_BIND_TO_MANAGER = 'doctrine.bindToManager';
41
	const TAG_REPOSITORY_ENTITY = 'doctrine.repositoryEntity';
42
	const DEFAULT_PROXY_NAMESPACE = 'Kdyby\GeneratedProxy';
43
	const KDYBY_METADATA_NAMESPACE = 'Kdyby\Doctrine';
44
45
	/**
46
	 * @var array
47
	 */
48
	public $managerDefaults = [
49
		'metadataCache' => 'default',
50
		'queryCache' => 'default',
51
		'resultCache' => 'default',
52
		'hydrationCache' => 'default',
53
		'secondLevelCache' => [
54
			'enabled' => FALSE,
55
			'factoryClass' => Doctrine\ORM\Cache\DefaultCacheFactory::class,
56
			'driver' => 'default',
57
			'regions' => [
58
				'defaultLifetime' => 3600,
59
				'defaultLockLifetime' => 60,
60
			],
61
			'fileLockRegionDirectory' => '%tempDir%/cache/Doctrine.Cache.Locks', // todo fix
62
			'logging' => '%debugMode%',
63
		],
64
		'classMetadataFactory' => Kdyby\Doctrine\Mapping\ClassMetadataFactory::class,
65
		'defaultRepositoryClassName' => Kdyby\Doctrine\EntityRepository::class,
66
		'repositoryFactoryClassName' => Kdyby\Doctrine\RepositoryFactory::class,
67
		'queryBuilderClassName' => Kdyby\Doctrine\QueryBuilder::class,
68
		'autoGenerateProxyClasses' => '%debugMode%',
69
		'namingStrategy' => Doctrine\ORM\Mapping\UnderscoreNamingStrategy::class,
70
		'quoteStrategy' => Doctrine\ORM\Mapping\DefaultQuoteStrategy::class,
71
		'entityListenerResolver' => Kdyby\Doctrine\Mapping\EntityListenerResolver::class,
72
		'proxyDir' => '%tempDir%/proxies',
73
		'proxyNamespace' => self::DEFAULT_PROXY_NAMESPACE,
74
		'dql' => ['string' => [], 'numeric' => [], 'datetime' => [], 'hints' => []],
75
		'hydrators' => [],
76
		'metadata' => [],
77
		'filters' => [],
78
		'namespaceAlias' => [],
79
		'targetEntityMappings' => [],
80
	];
81
82
	/**
83
	 * @var array
84
	 */
85
	public $connectionDefaults = [
86
		'dbname' => NULL,
87
		'host' => '127.0.0.1',
88
		'port' => NULL,
89
		'user' => NULL,
90
		'password' => NULL,
91
		'charset' => 'UTF8',
92
		'driver' => 'pdo_mysql',
93
		'driverClass' => NULL,
94
		'options' => NULL,
95
		'path' => NULL,
96
		'memory' => NULL,
97
		'unix_socket' => NULL,
98
		'logging' => '%debugMode%',
99
		'platformService' => NULL,
100
		'defaultTableOptions' => [],
101
		'resultCache' => 'default',
102
		'types' => [],
103
		'schemaFilter' => NULL,
104
	];
105
106
	/**
107
	 * @var array
108
	 */
109
	public $metadataDriverClasses = [
110
		self::ANNOTATION_DRIVER => Doctrine\ORM\Mapping\Driver\AnnotationDriver::class,
111
		'static' => Doctrine\Common\Persistence\Mapping\Driver\StaticPHPDriver::class,
112
		'yml' => Doctrine\ORM\Mapping\Driver\YamlDriver::class,
113
		'yaml' => Doctrine\ORM\Mapping\Driver\YamlDriver::class,
114
		'xml' => Doctrine\ORM\Mapping\Driver\XmlDriver::class,
115
		'db' => Doctrine\ORM\Mapping\Driver\DatabaseDriver::class,
116
	];
117
118
	/**
119
	 * @var array
120
	 */
121
	private $proxyAutoloaders = [];
122
123
	/**
124
	 * @var array
125
	 */
126
	private $targetEntityMappings = [];
127
128
	/**
129
	 * @var array
130
	 */
131
	private $configuredManagers = [];
132
133
	/**
134
	 * @var array
135
	 */
136
	private $managerConfigs = [];
137
138
	/**
139
	 * @var array
140
	 */
141
	private $configuredConnections = [];
142
143
144
145
	public function loadConfiguration()
146
	{
147
		$this->proxyAutoloaders =
148
		$this->targetEntityMappings =
149
		$this->configuredConnections =
150
		$this->managerConfigs =
151
		$this->configuredManagers = [];
152
153
		if (!$this->compiler->getExtensions(AnnotationsExtension::class)) {
154
			throw new Nette\Utils\AssertionException(sprintf("You should register %s before %s.", AnnotationsExtension::class, get_class($this)));
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
			/** @var mixed[] $emConfig */
183
			$emConfig = Nette\DI\Config\Helpers::merge($emConfig, $defaults);
184
			$this->processEntityManager($name, $emConfig);
185
		}
186
187
		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...
188
			if (!$this->isKdybyEventsPresent()) {
189
				throw new Nette\Utils\AssertionException('The option \'targetEntityMappings\' requires \'Kdyby\Events\DI\EventsExtension\'.', E_USER_NOTICE);
190
			}
191
192
			$listener = $builder->addDefinition($this->prefix('resolveTargetEntityListener'))
193
				->setClass(Kdyby\Doctrine\Tools\ResolveTargetEntityListener::class)
194
				->addTag(Kdyby\Events\DI\EventsExtension::TAG_SUBSCRIBER);
195
196
			foreach ($this->targetEntityMappings as $originalEntity => $mapping) {
197
				$listener->addSetup('addResolveTargetEntity', [$originalEntity, $mapping['targetEntity'], $mapping]);
198
			}
199
		}
200
201
		$this->loadConsole();
202
203
		$builder->addDefinition($this->prefix('registry'))
204
			->setClass(Kdyby\Doctrine\Registry::class, [
205
				$this->configuredConnections,
206
				$this->configuredManagers,
207
				$builder->parameters[$this->name]['dbal']['defaultConnection'],
208
				$builder->parameters[$this->name]['orm']['defaultEntityManager'],
209
			]);
210
	}
211
212
213
214
	protected function loadConsole()
215
	{
216
		$builder = $this->getContainerBuilder();
217
218
		foreach ($this->loadFromFile(__DIR__ . '/console.neon') as $i => $command) {
219
			$cli = $builder->addDefinition($this->prefix('cli.' . $i))
220
				->addTag(Kdyby\Console\DI\ConsoleExtension::TAG_COMMAND)
221
				->addTag(Nette\DI\Extensions\InjectExtension::TAG_INJECT, FALSE); // lazy injects
222
223
			if (is_string($command)) {
224
				$cli->setClass($command);
225
226
			} else {
227
				throw new Kdyby\Doctrine\NotSupportedException;
228
			}
229
		}
230
	}
231
232
233
234
	protected function processEntityManager($name, array $defaults)
235
	{
236
		$builder = $this->getContainerBuilder();
237
		$config = $this->resolveConfig($defaults, $this->managerDefaults, $this->connectionDefaults);
238
239
		if ($isDefault = !isset($builder->parameters[$this->name]['orm']['defaultEntityManager'])) {
240
			$builder->parameters[$this->name]['orm']['defaultEntityManager'] = $name;
241
		}
242
243
		$metadataDriver = $builder->addDefinition($this->prefix($name . '.metadataDriver'))
244
			->setClass(Doctrine\Common\Persistence\Mapping\Driver\MappingDriverChain::class)
245
			->setAutowired(FALSE);
246
		/** @var \Nette\DI\ServiceDefinition $metadataDriver */
247
248
		Validators::assertField($config, 'metadata', 'array');
249
		Validators::assertField($config, 'targetEntityMappings', 'array');
250
		$config['targetEntityMappings'] = $this->normalizeTargetEntityMappings($config['targetEntityMappings']);
251
		foreach ($this->compiler->getExtensions() as $extension) {
252
			if ($extension instanceof IEntityProvider) {
253
				$metadata = $extension->getEntityMappings();
254
				Validators::assert($metadata, 'array');
255
				foreach ($metadata as $namespace => $nsConfig) {
256
					if (array_key_exists($namespace, $config['metadata'])) {
257
						throw new Nette\Utils\AssertionException(sprintf('The namespace %s is already configured, provider cannot change it', $namespace));
258
					}
259
					$config['metadata'][$namespace] = $nsConfig;
260
				}
261
			}
262
263
			if ($extension instanceof ITargetEntityProvider) {
264
				$targetEntities = $extension->getTargetEntityMappings();
265
				Validators::assert($targetEntities, 'array');
266
				$config['targetEntityMappings'] = Nette\Utils\Arrays::mergeTree($config['targetEntityMappings'], $this->normalizeTargetEntityMappings($targetEntities));
267
			}
268
269
			if ($extension instanceof IDatabaseTypeProvider) {
270
				$providedTypes = $extension->getDatabaseTypes();
271
				Validators::assert($providedTypes, 'array');
272
273
				if (!isset($defaults['types'])) {
274
					$defaults['types'] = [];
275
				}
276
277
				$defaults['types'] = array_merge($defaults['types'], $providedTypes);
278
			}
279
		}
280
281
		foreach (self::natSortKeys($config['metadata']) as $namespace => $driver) {
282
			$this->processMetadataDriver($metadataDriver, $namespace, $driver, $name);
283
		}
284
285
		$this->processMetadataDriver($metadataDriver, self::KDYBY_METADATA_NAMESPACE, __DIR__ . '/../Entities', $name);
286
287
		if (empty($config['metadata'])) {
288
			$metadataDriver->addSetup('setDefaultDriver', [
289
				new Statement($this->metadataDriverClasses[self::ANNOTATION_DRIVER], [
290
					[$builder->expand('%appDir%')],
291
					2 => $this->prefix('@cache.' . $name . '.metadata')
292
				])
293
			]);
294
		}
295
296
		if ($config['repositoryFactoryClassName'] === 'default') {
297
			$config['repositoryFactoryClassName'] = DefaultRepositoryFactory::class;
298
		}
299
		$builder->addDefinition($this->prefix($name . '.repositoryFactory'))
300
			->setClass($config['repositoryFactoryClassName'])
301
			->setAutowired(FALSE);
302
303
		Validators::assertField($config, 'namespaceAlias', 'array');
304
		Validators::assertField($config, 'hydrators', 'array');
305
		Validators::assertField($config, 'dql', 'array');
306
		Validators::assertField($config['dql'], 'string', 'array');
307
		Validators::assertField($config['dql'], 'numeric', 'array');
308
		Validators::assertField($config['dql'], 'datetime', 'array');
309
		Validators::assertField($config['dql'], 'hints', 'array');
310
311
		$autoGenerateProxyClasses = is_bool($config['autoGenerateProxyClasses'])
312
			? ($config['autoGenerateProxyClasses'] ? AbstractProxyFactory::AUTOGENERATE_ALWAYS : AbstractProxyFactory::AUTOGENERATE_FILE_NOT_EXISTS)
313
			: $config['autoGenerateProxyClasses'];
314
315
		$configuration = $builder->addDefinition($this->prefix($name . '.ormConfiguration'))
316
			->setClass(Kdyby\Doctrine\Configuration::class)
317
			->addSetup('setMetadataCacheImpl', [$this->processCache($config['metadataCache'], $name . '.metadata')])
318
			->addSetup('setQueryCacheImpl', [$this->processCache($config['queryCache'], $name . '.query')])
319
			->addSetup('setResultCacheImpl', [$this->processCache($config['resultCache'], $name . '.ormResult')])
320
			->addSetup('setHydrationCacheImpl', [$this->processCache($config['hydrationCache'], $name . '.hydration')])
321
			->addSetup('setMetadataDriverImpl', [$this->prefix('@' . $name . '.metadataDriver')])
322
			->addSetup('setClassMetadataFactoryName', [$config['classMetadataFactory']])
323
			->addSetup('setDefaultRepositoryClassName', [$config['defaultRepositoryClassName']])
324
			->addSetup('setQueryBuilderClassName', [$config['queryBuilderClassName']])
325
			->addSetup('setRepositoryFactory', [$this->prefix('@' . $name . '.repositoryFactory')])
326
			->addSetup('setProxyDir', [$config['proxyDir']])
327
			->addSetup('setProxyNamespace', [$config['proxyNamespace']])
328
			->addSetup('setAutoGenerateProxyClasses', [$autoGenerateProxyClasses])
329
			->addSetup('setEntityNamespaces', [$config['namespaceAlias']])
330
			->addSetup('setCustomHydrationModes', [$config['hydrators']])
331
			->addSetup('setCustomStringFunctions', [$config['dql']['string']])
332
			->addSetup('setCustomNumericFunctions', [$config['dql']['numeric']])
333
			->addSetup('setCustomDatetimeFunctions', [$config['dql']['datetime']])
334
			->addSetup('setDefaultQueryHints', [$config['dql']['hints']])
335
			->addSetup('setNamingStrategy', CacheHelpers::filterArgs($config['namingStrategy']))
336
			->addSetup('setQuoteStrategy', CacheHelpers::filterArgs($config['quoteStrategy']))
337
			->addSetup('setEntityListenerResolver', CacheHelpers::filterArgs($config['entityListenerResolver']))
338
			->setAutowired(FALSE);
339
		/** @var Nette\DI\ServiceDefinition $configuration */
340
341
		$this->proxyAutoloaders[$config['proxyNamespace']] = $config['proxyDir'];
342
343
		$this->processSecondLevelCache($name, $config['secondLevelCache'], $isDefault);
344
345
		Validators::assertField($config, 'filters', 'array');
346
		foreach ($config['filters'] as $filterName => $filterClass) {
347
			$configuration->addSetup('addFilter', [$filterName, $filterClass]);
348
		}
349
350
		if ($config['targetEntityMappings']) {
351
			$configuration->addSetup('setTargetEntityMap', [array_map(function ($mapping) {
352
				return $mapping['targetEntity'];
353
			}, $config['targetEntityMappings'])]);
354
			$this->targetEntityMappings = Nette\Utils\Arrays::mergeTree($this->targetEntityMappings, $config['targetEntityMappings']);
355
		}
356
357
		if ($this->isKdybyEventsPresent()) {
358
			$builder->addDefinition($this->prefix($name . '.evm'))
359
				->setClass(Kdyby\Events\NamespacedEventManager::class, [Kdyby\Doctrine\Events::NS . '::'])
360
				->addSetup('$dispatchGlobalEvents', [TRUE]) // for BC
361
				->setAutowired(FALSE);
362
363
		} else {
364
			$builder->addDefinition($this->prefix($name . '.evm'))
365
				->setClass('Doctrine\Common\EventManager')
366
				->setAutowired(FALSE);
367
		}
368
369
		// entity manager
370
		$entityManager = $builder->addDefinition($managerServiceId = $this->prefix($name . '.entityManager'))
371
			->setClass(Kdyby\Doctrine\EntityManager::class)
372
			->setFactory(Kdyby\Doctrine\EntityManager::class . '::create', [
373
				$connectionService = $this->processConnection($name, $defaults, $isDefault),
374
				$this->prefix('@' . $name . '.ormConfiguration'),
375
				$this->prefix('@' . $name . '.evm'),
376
			])
377
			->addTag(self::TAG_ENTITY_MANAGER)
378
			->addTag('kdyby.doctrine.entityManager')
379
			->setAutowired($isDefault);
380
381
		if ($this->isTracyPresent()) {
382
			$entityManager->addSetup('?->bindEntityManager(?)', [$this->prefix('@' . $name . '.diagnosticsPanel'), '@self']);
383
		}
384
385
		$builder->addDefinition($this->prefix('repositoryFactory.' . $name . '.defaultRepositoryFactory'))
386
				->setClass($config['defaultRepositoryClassName'])
387
				->setImplement(IRepositoryFactory::class)
388
				->setArguments([new Code\PhpLiteral('$entityManager'), new Code\PhpLiteral('$classMetadata')])
389
				->setParameters([EntityManagerInterface::class . ' entityManager', Doctrine\ORM\Mapping\ClassMetadata::class . ' classMetadata'])
390
				->setAutowired(FALSE);
391
392
		$builder->addDefinition($this->prefix($name . '.schemaValidator'))
393
			->setClass(Doctrine\ORM\Tools\SchemaValidator::class, ['@' . $managerServiceId])
394
			->setAutowired($isDefault);
395
396
		$builder->addDefinition($this->prefix($name . '.schemaTool'))
397
			->setClass(Doctrine\ORM\Tools\SchemaTool::class, ['@' . $managerServiceId])
398
			->setAutowired($isDefault);
399
400
		$cacheCleaner = $builder->addDefinition($this->prefix($name . '.cacheCleaner'))
401
			->setClass(Kdyby\Doctrine\Tools\CacheCleaner::class, ['@' . $managerServiceId])
402
			->setAutowired($isDefault);
403
404
		$builder->addDefinition($this->prefix($name . '.schemaManager'))
405
			->setClass(AbstractSchemaManager::class)
406
			->setFactory('@' . Kdyby\Doctrine\Connection::class . '::getSchemaManager')
407
			->setAutowired($isDefault);
408
409
		foreach ($this->compiler->getExtensions(AnnotationsExtension::class) as $extension) {
410
			/** @var AnnotationsExtension $extension */
411
			$cacheCleaner->addSetup('addCacheStorage', [$extension->prefix('@cache.annotations')]);
412
		}
413
414
		if ($isDefault) {
415
			$builder->addDefinition($this->prefix('helper.entityManager'))
416
				->setClass(EntityManagerHelper::class, ['@' . $managerServiceId])
417
				->addTag(Kdyby\Console\DI\ConsoleExtension::HELPER_TAG, 'em');
418
419
			$builder->addDefinition($this->prefix('helper.connection'))
420
				->setClass(ConnectionHelper::class, [$connectionService])
421
				->addTag(Kdyby\Console\DI\ConsoleExtension::HELPER_TAG, 'db');
422
423
			$builder->addAlias($this->prefix('schemaValidator'), $this->prefix($name . '.schemaValidator'));
424
			$builder->addAlias($this->prefix('schemaTool'), $this->prefix($name . '.schemaTool'));
425
			$builder->addAlias($this->prefix('cacheCleaner'), $this->prefix($name . '.cacheCleaner'));
426
			$builder->addAlias($this->prefix('schemaManager'), $this->prefix($name . '.schemaManager'));
427
		}
428
429
		$this->configuredManagers[$name] = $managerServiceId;
430
		$this->managerConfigs[$name] = $config;
431
	}
432
433
434
435
	protected function processSecondLevelCache($name, array $config, $isDefault)
436
	{
437
		if (!$config['enabled']) {
438
			return;
439
		}
440
441
		$builder = $this->getContainerBuilder();
442
443
		$cacheFactory = $builder->addDefinition($this->prefix($name . '.cacheFactory'))
444
			->setClass(Doctrine\ORM\Cache\CacheFactory::class)
445
			->setFactory($config['factoryClass'], [
446
				$this->prefix('@' . $name . '.cacheRegionsConfiguration'),
447
				$this->processCache($config['driver'], $name . '.secondLevel'),
448
			])
449
			->setAutowired($isDefault);
450
451
		if ($config['factoryClass'] === $this->managerDefaults['secondLevelCache']['factoryClass']
452
			|| is_subclass_of($config['factoryClass'], $this->managerDefaults['secondLevelCache']['factoryClass'])
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if $this->managerDefaults['...Cache']['factoryClass'] can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
453
		) {
454
			$cacheFactory->addSetup('setFileLockRegionDirectory', [$config['fileLockRegionDirectory']]);
455
		}
456
457
		$builder->addDefinition($this->prefix($name . '.cacheRegionsConfiguration'))
458
			->setClass(Doctrine\ORM\Cache\RegionsConfiguration::class, [
459
				$config['regions']['defaultLifetime'],
460
				$config['regions']['defaultLockLifetime'],
461
			])
462
			->setAutowired($isDefault);
463
464
		$logger = $builder->addDefinition($this->prefix($name . '.cacheLogger'))
465
			->setClass(Doctrine\ORM\Cache\Logging\CacheLogger::class)
466
			->setFactory(Doctrine\ORM\Cache\Logging\CacheLoggerChain::class)
467
			->setAutowired(FALSE);
468
469
		if ($config['logging']) {
470
			$logger->addSetup('setLogger', [
471
				'statistics',
472
				new Statement(Doctrine\ORM\Cache\Logging\StatisticsCacheLogger::class)
473
			]);
474
		}
475
476
		$builder->addDefinition($cacheConfigName = $this->prefix($name . '.ormCacheConfiguration'))
477
			->setClass(Doctrine\ORM\Cache\CacheConfiguration::class)
478
			->addSetup('setCacheFactory', [$this->prefix('@' . $name . '.cacheFactory')])
479
			->addSetup('setCacheLogger', [$this->prefix('@' . $name . '.cacheLogger')])
480
			->setAutowired($isDefault);
481
482
		$configuration = $builder->getDefinition($this->prefix($name . '.ormConfiguration'));
483
		$configuration->addSetup('setSecondLevelCacheEnabled');
484
		$configuration->addSetup('setSecondLevelCacheConfiguration', ['@' . $cacheConfigName]);
485
	}
486
487
488
489
	protected function processConnection($name, array $defaults, $isDefault = FALSE)
490
	{
491
		$builder = $this->getContainerBuilder();
492
		$config = $this->resolveConfig($defaults, $this->connectionDefaults, $this->managerDefaults);
493
494
		if ($isDefault) {
495
			$builder->parameters[$this->name]['dbal']['defaultConnection'] = $name;
496
		}
497
498
		if (isset($defaults['connection'])) {
499
			return $this->prefix('@' . $defaults['connection'] . '.connection');
500
		}
501
502
		// config
503
		$configuration = $builder->addDefinition($this->prefix($name . '.dbalConfiguration'))
504
			->setClass(Doctrine\DBAL\Configuration::class)
505
			->addSetup('setResultCacheImpl', [$this->processCache($config['resultCache'], $name . '.dbalResult')])
506
			->addSetup('setSQLLogger', [new Statement(Doctrine\DBAL\Logging\LoggerChain::class)])
507
			->addSetup('setFilterSchemaAssetsExpression', [$config['schemaFilter']])
508
			->setAutowired(FALSE);
509
510
		// types
511
		Validators::assertField($config, 'types', 'array');
512
		$schemaTypes = $dbalTypes = [];
513
		foreach ($config['types'] as $dbType => $className) {
514
			/** @var Doctrine\DBAL\Types\Type $typeInst */
515
			$typeInst = Code\Helpers::createObject($className, []);
516
			$dbalTypes[$typeInst->getName()] = $className;
517
			$schemaTypes[$dbType] = $typeInst->getName();
518
		}
519
520
		// tracy panel
521
		if ($this->isTracyPresent()) {
522
			$builder->addDefinition($this->prefix($name . '.diagnosticsPanel'))
523
				->setClass(Kdyby\Doctrine\Diagnostics\Panel::class)
524
				->setAutowired(FALSE);
525
		}
526
527
		// connection
528
		$options = array_diff_key($config, array_flip(['types', 'resultCache', 'connection', 'logging']));
529
		$connection = $builder->addDefinition($connectionServiceId = $this->prefix($name . '.connection'))
530
			->setClass(Kdyby\Doctrine\Connection::class)
531
			->setFactory(Kdyby\Doctrine\Connection::class . '::create', [
532
				$options,
533
				$this->prefix('@' . $name . '.dbalConfiguration'),
534
				$this->prefix('@' . $name . '.evm'),
535
			])
536
			->addSetup('setSchemaTypes', [$schemaTypes])
537
			->addSetup('setDbalTypes', [$dbalTypes])
538
			->addTag(self::TAG_CONNECTION)
539
			->addTag('kdyby.doctrine.connection')
540
			->setAutowired($isDefault);
541
542
		if ($this->isTracyPresent()) {
543
			$connection->addSetup('$panel = ?->bindConnection(?)', [$this->prefix('@' . $name . '.diagnosticsPanel'), '@self']);
544
		}
545
546
		/** @var Nette\DI\ServiceDefinition $connection */
547
548
		$this->configuredConnections[$name] = $connectionServiceId;
549
550
		if (!is_bool($config['logging'])) {
551
			$fileLogger = new Statement(Kdyby\Doctrine\Diagnostics\FileLogger::class, [$builder->expand($config['logging'])]);
552
			$configuration->addSetup('$service->getSQLLogger()->addLogger(?)', [$fileLogger]);
553
554
		} elseif ($config['logging']) {
555
			$connection->addSetup('?->enableLogging()', [new Code\PhpLiteral('$panel')]);
556
		}
557
558
		return $this->prefix('@' . $name . '.connection');
559
	}
560
561
562
563
	/**
564
	 * @param \Nette\DI\ServiceDefinition $metadataDriver
565
	 * @param string $namespace
566
	 * @param string|array|\stdClass $driver
567
	 * @param string $prefix
568
	 * @throws \Nette\Utils\AssertionException
569
	 * @return string
570
	 */
571
	protected function processMetadataDriver(Nette\DI\ServiceDefinition $metadataDriver, $namespace, $driver, $prefix)
572
	{
573
		if (!is_string($namespace) || !Strings::match($namespace, '#^' . self::PHP_NAMESPACE . '\z#')) {
574
			throw new Nette\Utils\AssertionException("The metadata namespace expects to be valid namespace, $namespace given.");
575
		}
576
		$namespace = ltrim($namespace, '\\');
577
578
		if (is_string($driver) && strpos($driver, '@') === 0) { // service reference
579
			$metadataDriver->addSetup('addDriver', [$driver, $namespace]);
580
			return $driver;
581
		}
582
583
		if (is_string($driver) || is_array($driver)) {
584
			$paths = is_array($driver) ? $driver : [$driver];
585
			foreach ($paths as $path) {
586
				if (!file_exists($path)) {
587
					throw new Nette\Utils\AssertionException("The metadata path expects to be an existing directory, $path given.");
588
				}
589
			}
590
591
			$driver = new Statement(self::ANNOTATION_DRIVER, is_array($paths) ? $paths : [$paths]);
592
		}
593
594
		$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...
595
		list($driver) = CacheHelpers::filterArgs($driver);
596
		/** @var Statement $driver */
597
598
		/** @var string $impl */
599
		if (isset($this->metadataDriverClasses[$impl])) {
600
			$driver = new Statement($this->metadataDriverClasses[$impl], $driver->arguments);
601
		}
602
603
		if (is_string($driver->getEntity()) && substr($driver->getEntity(), 0, 1) === '@') {
604
			$metadataDriver->addSetup('addDriver', [$driver->getEntity(), $namespace]);
605
			return $driver->getEntity();
606
		}
607
608
		if ($impl === self::ANNOTATION_DRIVER) {
609
			$driver->arguments = [
610
				'@' . self::ANNOTATION_DRIVER . '.reader',
611
				Nette\Utils\Arrays::flatten($driver->arguments)
612
			];
613
		}
614
615
		$serviceName = $this->prefix($prefix . '.driver.' . str_replace('\\', '_', $namespace) . '.' . str_replace('\\', '_', $impl) . 'Impl');
616
617
		$this->getContainerBuilder()->addDefinition($serviceName)
618
			->setClass(Doctrine\Common\Persistence\Mapping\Driver\MappingDriver::class)
619
			->setFactory($driver->getEntity(), $driver->arguments)
620
			->setAutowired(FALSE);
621
622
		$metadataDriver->addSetup('addDriver', ['@' . $serviceName, $namespace]);
623
		return '@' . $serviceName;
624
	}
625
626
627
628
	/**
629
	 * @param string|\stdClass $cache
630
	 * @param string $suffix
631
	 * @return string
632
	 */
633
	protected function processCache($cache, $suffix)
634
	{
635
		return CacheHelpers::processCache($this, $cache, $suffix, $this->getContainerBuilder()->parameters[$this->prefix('debug')]);
636
	}
637
638
639
640
	public function beforeCompile()
641
	{
642
		$this->processRepositories();
643
		$this->processEventManagers();
644
	}
645
646
647
648
	protected function processRepositories()
649
	{
650
		$builder = $this->getContainerBuilder();
651
652
		$disabled = TRUE;
653
		foreach ($this->configuredManagers as $managerName => $_) {
654
			$factoryClassName = $builder->getDefinition($this->prefix($managerName . '.repositoryFactory'))->getClass();
655
			if ($factoryClassName === Kdyby\Doctrine\RepositoryFactory::class || in_array(Kdyby\Doctrine\RepositoryFactory::class, class_parents($factoryClassName), TRUE)) {
656
				$disabled = FALSE;
657
			}
658
		}
659
660
		if ($disabled) {
661
			return;
662
		}
663
664
		if (!method_exists($builder, 'findByType')) {
665
			foreach ($this->configuredManagers as $managerName => $_) {
666
				$builder->getDefinition($this->prefix($managerName . '.repositoryFactory'))
667
					->addSetup('setServiceIdsMap', [[], $this->prefix('repositoryFactory.' . $managerName . '.defaultRepositoryFactory')]);
668
			}
669
670
			return;
671
		}
672
673
		$serviceMap = array_fill_keys(array_keys($this->configuredManagers), []);
674
		foreach ($builder->findByType(Doctrine\ORM\EntityRepository::class) as $originalServiceName => $originalDef) {
675
			if (strpos($originalServiceName, $this->name . '.') === 0) {
676
				continue; // ignore
677
			}
678
679
			$originalDefFactory = $originalDef->getFactory();
680
			$factory = ($originalDefFactory !== NULL) ? $originalDefFactory->getEntity() : $originalDef->getClass();
681
			if (stripos($factory, '::getRepository') !== FALSE) {
682
				continue; // ignore
683
			}
684
685
			$factoryServiceName = $this->prefix('repositoryFactory.' . $originalServiceName);
686
			$factoryDef = $builder->addDefinition($factoryServiceName, $originalDef)
687
				->setImplement(IRepositoryFactory::class)
688
				->setParameters([Doctrine\ORM\EntityManagerInterface::class . ' entityManager', Doctrine\ORM\Mapping\ClassMetadata::class . ' classMetadata'])
689
				->setAutowired(FALSE);
690
			$factoryStatement = $factoryDef->getFactory() ?: new Statement($factoryDef->getClass());
691
			$factoryStatement->arguments[0] = new Code\PhpLiteral('$entityManager');
692
			$factoryStatement->arguments[1] = new Code\PhpLiteral('$classMetadata');
693
			$factoryDef->setArguments($factoryStatement->arguments);
694
695
			$boundManagers = $this->getServiceBoundManagers($originalDef);
696
			Validators::assert($boundManagers, 'list:1', 'bound manager');
697
698
			if ($boundEntity = $originalDef->getTag(self::TAG_REPOSITORY_ENTITY)) {
699
				if (!is_string($boundEntity) || !class_exists($boundEntity)) {
700
					throw new Nette\Utils\AssertionException(sprintf('The entity "%s" for repository "%s" cannot be autoloaded.', $boundEntity, $originalDef->getClass()));
701
				}
702
				$entityArgument = $boundEntity;
703
704
			} else {
705
				throw new Nette\Utils\AssertionException(sprintf(
706
					'The magic auto-detection of entity for repository %s for %s was removed from Kdyby.' .
707
					'You have to specify %s tag with classname of the related entity in order to use this feature.',
708
					$originalDef->getClass(),
709
					IRepositoryFactory::class,
710
					self::TAG_REPOSITORY_ENTITY
711
				));
712
			}
713
714
			$builder->removeDefinition($originalServiceName);
715
			$builder->addDefinition($originalServiceName)
716
				->setClass($originalDef->getClass())
717
				->setFactory(sprintf('@%s::getRepository', $this->configuredManagers[$boundManagers[0]]), [$entityArgument]);
718
719
			$serviceMap[$boundManagers[0]][$originalDef->getClass()] = $factoryServiceName;
720
		}
721
722
		foreach ($this->configuredManagers as $managerName => $_) {
723
			$builder->getDefinition($this->prefix($managerName . '.repositoryFactory'))
724
				->addSetup('setServiceIdsMap', [
725
					$serviceMap[$managerName],
726
					$this->prefix('repositoryFactory.' . $managerName . '.defaultRepositoryFactory')
727
				]);
728
		}
729
	}
730
731
732
733
	protected function processEventManagers()
734
	{
735
		$builder = $this->getContainerBuilder();
736
		$customEvmService = $builder->getByType(\Doctrine\Common\EventManager::class);
737
		if ($this->isKdybyEventsPresent() || !$customEvmService) {
738
			return;
739
		}
740
741
		foreach ($this->configuredManagers as $managerName => $_) {
742
			$builder->getDefinition($this->prefix($managerName . '.evm'))->setFactory('@' . $customEvmService);
743
		}
744
	}
745
746
747
748
	/**
749
	 * @param Nette\DI\ServiceDefinition $def
750
	 * @return string[]
751
	 */
752
	protected function getServiceBoundManagers(Nette\DI\ServiceDefinition $def)
753
	{
754
		$builder = $this->getContainerBuilder();
755
		$boundManagers = $def->getTag(self::TAG_BIND_TO_MANAGER);
756
757
		return is_array($boundManagers) ? $boundManagers : [$builder->parameters[$this->name]['orm']['defaultEntityManager']];
758
	}
759
760
761
762
	public function afterCompile(Code\ClassType $class)
763
	{
764
		$init = $class->getMethod('initialize');
765
766
		if ($this->isTracyPresent()) {
767
			$init->addBody('?::registerBluescreen($this);', [new Code\PhpLiteral(Kdyby\Doctrine\Diagnostics\Panel::class)]);
768
			$this->addCollapsePathsToTracy($init);
769
		}
770
771
		foreach ($this->proxyAutoloaders as $namespace => $dir) {
772
			$originalInitialize = $init->getBody();
773
			$init->setBody('?::create(?, ?)->register();', [new Code\PhpLiteral(Kdyby\Doctrine\Proxy\ProxyAutoloader::class), $dir, $namespace]);
774
			$init->addBody((string) $originalInitialize);
775
		}
776
	}
777
778
779
780
	/**
781
     * @param array $provided
782
     * @param array $defaults
783
     * @param array $diff
784
	 * @return array
785
	 */
786
	private function resolveConfig(array $provided, array $defaults, array $diff = [])
787
	{
788
		return $this->getContainerBuilder()->expand(Nette\DI\Config\Helpers::merge(
789
			array_diff_key($provided, array_diff_key($diff, $defaults)),
790
			$defaults
791
		));
792
	}
793
794
795
	/**
796
	 * @param array $targetEntityMappings
797
	 * @return array
798
	 */
799
	private function normalizeTargetEntityMappings(array $targetEntityMappings)
800
	{
801
		$normalized = [];
802
		foreach ($targetEntityMappings as $originalEntity => $targetEntity) {
803
			$originalEntity = ltrim($originalEntity, '\\');
804
			Validators::assert($targetEntity, 'array|string');
805
			if (is_array($targetEntity)) {
806
				Validators::assertField($targetEntity, 'targetEntity', 'string');
807
				$mapping = array_merge($targetEntity, [
808
					'targetEntity' => ltrim($targetEntity['targetEntity'], '\\')
809
				]);
810
811
			} else {
812
				$mapping = [
813
					'targetEntity' => ltrim($targetEntity, '\\'),
814
				];
815
			}
816
			$normalized[$originalEntity] = $mapping;
817
		}
818
		return $normalized;
819
	}
820
821
822
823
	/**
824
	 * @return bool
825
	 */
826
	private function isTracyPresent()
827
	{
828
		return interface_exists(\Tracy\IBarPanel::class);
829
	}
830
831
832
833
	private function addCollapsePathsToTracy(Method $init)
834
	{
835
		$blueScreen = \Tracy\Debugger::class . '::getBlueScreen()';
836
		$commonDirname = dirname(Nette\Reflection\ClassType::from(Doctrine\Common\Version::class)->getFileName());
837
838
		$init->addBody($blueScreen . '->collapsePaths[] = ?;', [dirname(Nette\Reflection\ClassType::from(Kdyby\Doctrine\Exception::class)->getFileName())]);
839
		$init->addBody($blueScreen . '->collapsePaths[] = ?;', [dirname(dirname(dirname(dirname($commonDirname))))]); // this should be vendor/doctrine
840
		foreach ($this->proxyAutoloaders as $dir) {
841
			$init->addBody($blueScreen . '->collapsePaths[] = ?;', [$dir]);
842
		}
843
	}
844
845
846
847
	/**
848
	 * @param \Nette\Configurator $configurator
849
	 */
850
	public static function register(Nette\Configurator $configurator)
851
	{
852
		$configurator->onCompile[] = function ($config, Nette\DI\Compiler $compiler) {
853
			$compiler->addExtension('doctrine', new OrmExtension());
854
		};
855
	}
856
857
858
859
	/**
860
	 * @param array $array
861
	 */
862
	private static function natSortKeys(array &$array)
863
	{
864
		$keys = array_keys($array);
865
		natsort($keys);
866
		$keys = array_flip(array_reverse($keys, TRUE));
867
		$array = array_merge($keys, $array);
868
		return $array;
869
	}
870
871
}
872