Completed
Push — master ( 7be1d1...3a90ba )
by Filip
02:07
created

OrmExtension::processRepositoryFactoryEntities()   C

Complexity

Conditions 8
Paths 11

Size

Total Lines 38
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 38
rs 5.3846
c 0
b 0
f 0
cc 8
eloc 21
nc 11
nop 1

2 Methods

Rating   Name   Duplication   Size   Complexity  
A OrmExtension::resolveConfig() 0 7 1
A OrmExtension::normalizeTargetEntityMappings() 0 21 3
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
139
	public function loadConfiguration()
140
	{
141
		$this->proxyAutoloaders =
142
		$this->targetEntityMappings =
143
		$this->configuredConnections =
144
		$this->managerConfigs =
145
		$this->configuredManagers = [];
146
147
		if (!$this->compiler->getExtensions('Kdyby\Annotations\DI\AnnotationsExtension')) {
148
			throw new Nette\Utils\AssertionException('You should register \'Kdyby\Annotations\DI\AnnotationsExtension\' before \'' . get_class($this) . '\'.', E_USER_NOTICE);
149
		}
150
151
		$builder = $this->getContainerBuilder();
152
		$config = $this->getConfig();
153
154
		$builder->parameters[$this->prefix('debug')] = !empty($config['debug']);
155
		if (isset($config['dbname']) || isset($config['driver']) || isset($config['connection'])) {
156
			$config = ['default' => $config];
157
			$defaults = ['debug' => $builder->parameters['debugMode']];
158
159
		} else {
160
			$defaults = array_intersect_key($config, $this->managerDefaults)
161
				+ array_intersect_key($config, $this->connectionDefaults)
162
				+ ['debug' => $builder->parameters['debugMode']];
163
164
			$config = array_diff_key($config, $defaults);
165
		}
166
167
		if (empty($config)) {
168
			throw new Kdyby\Doctrine\UnexpectedValueException("Please configure the Doctrine extensions using the section '{$this->name}:' in your config file.");
169
		}
170
171
		foreach ($config as $name => $emConfig) {
172
			if (!is_array($emConfig) || (empty($emConfig['dbname']) && empty($emConfig['driver']))) {
173
				throw new Kdyby\Doctrine\UnexpectedValueException("Please configure the Doctrine extensions using the section '{$this->name}:' in your config file.");
174
			}
175
176
			$emConfig = Nette\DI\Config\Helpers::merge($emConfig, $defaults);
177
			$this->processEntityManager($name, $emConfig);
178
		}
179
180
		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...
181
			if (!$this->isKdybyEventsPresent()) {
182
				throw new Nette\Utils\AssertionException('The option \'targetEntityMappings\' requires \'Kdyby\Events\DI\EventsExtension\'.', E_USER_NOTICE);
183
			}
184
185
			$listener = $builder->addDefinition($this->prefix('resolveTargetEntityListener'))
186
				->setClass('Kdyby\Doctrine\Tools\ResolveTargetEntityListener')
187
				->addTag(Kdyby\Events\DI\EventsExtension::TAG_SUBSCRIBER);
188
189
			foreach ($this->targetEntityMappings as $originalEntity => $mapping) {
190
				$listener->addSetup('addResolveTargetEntity', [$originalEntity, $mapping['targetEntity'], $mapping]);
191
			}
192
		}
193
194
		$this->loadConsole();
195
196
		$builder->addDefinition($this->prefix('registry'))
197
			->setClass('Kdyby\Doctrine\Registry', [
198
				$this->configuredConnections,
199
				$this->configuredManagers,
200
				$builder->parameters[$this->name]['dbal']['defaultConnection'],
201
				$builder->parameters[$this->name]['orm']['defaultEntityManager'],
202
			]);
203
	}
204
205
206
207
	protected function loadConsole()
208
	{
209
		$builder = $this->getContainerBuilder();
210
211
		foreach ($this->loadFromFile(__DIR__ . '/console.neon') as $i => $command) {
212
			$cli = $builder->addDefinition($this->prefix('cli.' . $i))
213
				->addTag(Kdyby\Console\DI\ConsoleExtension::TAG_COMMAND)
214
				->addTag(Nette\DI\Extensions\InjectExtension::TAG_INJECT, FALSE); // lazy injects
215
216
			if (is_string($command)) {
217
				$cli->setClass($command);
218
219
			} else {
220
				throw new Kdyby\Doctrine\NotSupportedException;
221
			}
222
		}
223
	}
224
225
226
227
	protected function processEntityManager($name, array $defaults)
228
	{
229
		$builder = $this->getContainerBuilder();
230
		$config = $this->resolveConfig($defaults, $this->managerDefaults, $this->connectionDefaults);
231
232
		if ($isDefault = !isset($builder->parameters[$this->name]['orm']['defaultEntityManager'])) {
233
			$builder->parameters[$this->name]['orm']['defaultEntityManager'] = $name;
234
		}
235
236
		$metadataDriver = $builder->addDefinition($this->prefix($name . '.metadataDriver'))
237
			->setClass('Doctrine\Common\Persistence\Mapping\Driver\MappingDriverChain')
238
			->setAutowired(FALSE);
239
		/** @var Nette\DI\ServiceDefinition $metadataDriver */
240
241
		Validators::assertField($config, 'metadata', 'array');
242
		Validators::assertField($config, 'targetEntityMappings', 'array');
243
		$config['targetEntityMappings'] = $this->normalizeTargetEntityMappings($config['targetEntityMappings']);
244
		foreach ($this->compiler->getExtensions() as $extension) {
245
			if ($extension instanceof IEntityProvider) {
246
				$metadata = $extension->getEntityMappings();
247
				Validators::assert($metadata, 'array');
248
				foreach ($metadata as $namespace => $config) {
249
					if (array_key_exists($namespace, $config['metadata'])) {
250
						throw new Nette\Utils\AssertionException(sprintf('The namespace %s is already configured, provider cannot change it', $namespace));
251
					}
252
					$config['metadata'][$namespace] = $config;
253
				}
254
			}
255
256
			if ($extension instanceof ITargetEntityProvider) {
257
				$targetEntities = $extension->getTargetEntityMappings();
258
				Validators::assert($targetEntities, 'array');
259
				$config['targetEntityMappings'] = Nette\Utils\Arrays::mergeTree($config['targetEntityMappings'], $this->normalizeTargetEntityMappings($targetEntities));
260
			}
261
262
			if ($extension instanceof IDatabaseTypeProvider) {
263
				$providedTypes = $extension->getDatabaseTypes();
264
				Validators::assert($providedTypes, 'array');
265
266
				if (!isset($defaults['types'])) {
267
					$defaults['types'] = [];
268
				}
269
270
				$defaults['types'] = array_merge($defaults['types'], $providedTypes);
271
			}
272
		}
273
274
		foreach (self::natSortKeys($config['metadata']) as $namespace => $driver) {
275
			$this->processMetadataDriver($metadataDriver, $namespace, $driver, $name);
276
		}
277
278
		$this->processMetadataDriver($metadataDriver, 'Kdyby\\Doctrine', __DIR__ . '/../Entities', $name);
279
280
		if (empty($config['metadata'])) {
281
			$metadataDriver->addSetup('setDefaultDriver', [
282
				new Statement($this->metadataDriverClasses[self::ANNOTATION_DRIVER], [
283
					[$builder->expand('%appDir%')],
284
					2 => $this->prefix('@cache.' . $name . '.metadata')
285
				])
286
			]);
287
		}
288
289
		if ($config['repositoryFactoryClassName'] === 'default') {
290
			$config['repositoryFactoryClassName'] = 'Doctrine\ORM\Repository\DefaultRepositoryFactory';
291
		}
292
		$builder->addDefinition($this->prefix($name . '.repositoryFactory'))
293
			->setClass($config['repositoryFactoryClassName'])
294
			->setAutowired(FALSE);
295
296
		Validators::assertField($config, 'namespaceAlias', 'array');
297
		Validators::assertField($config, 'hydrators', 'array');
298
		Validators::assertField($config, 'dql', 'array');
299
		Validators::assertField($config['dql'], 'string', 'array');
300
		Validators::assertField($config['dql'], 'numeric', 'array');
301
		Validators::assertField($config['dql'], 'datetime', 'array');
302
		Validators::assertField($config['dql'], 'hints', 'array');
303
304
		$autoGenerateProxyClasses = is_bool($config['autoGenerateProxyClasses'])
305
			? ($config['autoGenerateProxyClasses'] ? AbstractProxyFactory::AUTOGENERATE_ALWAYS : AbstractProxyFactory::AUTOGENERATE_NEVER)
306
			: $config['autoGenerateProxyClasses'];
307
308
		$configuration = $builder->addDefinition($this->prefix($name . '.ormConfiguration'))
309
			->setClass('Kdyby\Doctrine\Configuration')
310
			->addSetup('setMetadataCacheImpl', [$this->processCache($config['metadataCache'], $name . '.metadata')])
311
			->addSetup('setQueryCacheImpl', [$this->processCache($config['queryCache'], $name . '.query')])
312
			->addSetup('setResultCacheImpl', [$this->processCache($config['resultCache'], $name . '.ormResult')])
313
			->addSetup('setHydrationCacheImpl', [$this->processCache($config['hydrationCache'], $name . '.hydration')])
314
			->addSetup('setMetadataDriverImpl', [$this->prefix('@' . $name . '.metadataDriver')])
315
			->addSetup('setClassMetadataFactoryName', [$config['classMetadataFactory']])
316
			->addSetup('setDefaultRepositoryClassName', [$config['defaultRepositoryClassName']])
317
			->addSetup('setQueryBuilderClassName', [$config['queryBuilderClassName']])
318
			->addSetup('setRepositoryFactory', [$this->prefix('@' . $name . '.repositoryFactory')])
319
			->addSetup('setProxyDir', [$config['proxyDir']])
320
			->addSetup('setProxyNamespace', [$config['proxyNamespace']])
321
			->addSetup('setAutoGenerateProxyClasses', [$autoGenerateProxyClasses])
322
			->addSetup('setEntityNamespaces', [$config['namespaceAlias']])
323
			->addSetup('setCustomHydrationModes', [$config['hydrators']])
324
			->addSetup('setCustomStringFunctions', [$config['dql']['string']])
325
			->addSetup('setCustomNumericFunctions', [$config['dql']['numeric']])
326
			->addSetup('setCustomDatetimeFunctions', [$config['dql']['datetime']])
327
			->addSetup('setDefaultQueryHints', [$config['dql']['hints']])
328
			->addSetup('setNamingStrategy', CacheHelpers::filterArgs($config['namingStrategy']))
329
			->addSetup('setQuoteStrategy', CacheHelpers::filterArgs($config['quoteStrategy']))
330
			->addSetup('setEntityListenerResolver', CacheHelpers::filterArgs($config['entityListenerResolver']))
331
			->setAutowired(FALSE);
332
		/** @var Nette\DI\ServiceDefinition $configuration */
333
334
		$this->proxyAutoloaders[$config['proxyNamespace']] = $config['proxyDir'];
335
336
		$this->processSecondLevelCache($name, $config['secondLevelCache'], $isDefault);
337
338
		Validators::assertField($config, 'filters', 'array');
339
		foreach ($config['filters'] as $filterName => $filterClass) {
340
			$configuration->addSetup('addFilter', [$filterName, $filterClass]);
341
		}
342
343
		if ($config['targetEntityMappings']) {
344
			$configuration->addSetup('setTargetEntityMap', [array_map(function ($mapping) {
345
				return $mapping['targetEntity'];
346
			}, $config['targetEntityMappings'])]);
347
			$this->targetEntityMappings = Nette\Utils\Arrays::mergeTree($this->targetEntityMappings, $config['targetEntityMappings']);
348
		}
349
350
		if ($this->isKdybyEventsPresent()) {
351
			$builder->addDefinition($this->prefix($name . '.evm'))
352
				->setClass('Kdyby\Events\NamespacedEventManager', [Kdyby\Doctrine\Events::NS . '::'])
353
				->addSetup('$dispatchGlobalEvents', [TRUE]) // for BC
354
				->setAutowired(FALSE);
355
356
		} else {
357
			$builder->addDefinition($this->prefix($name . '.evm'))
358
				->setClass('Doctrine\Common\EventManager')
359
				->setAutowired(FALSE);
360
		}
361
362
		// entity manager
363
		$entityManager = $builder->addDefinition($managerServiceId = $this->prefix($name . '.entityManager'))
364
			->setClass('Kdyby\Doctrine\EntityManager')
365
			->setFactory('Kdyby\Doctrine\EntityManager::create', [
366
				$connectionService = $this->processConnection($name, $defaults, $isDefault),
367
				$this->prefix('@' . $name . '.ormConfiguration'),
368
				$this->prefix('@' . $name . '.evm'),
369
			])
370
			->addTag(self::TAG_ENTITY_MANAGER)
371
			->addTag('kdyby.doctrine.entityManager')
372
			->setAutowired($isDefault);
373
374
		if ($this->isTracyPresent()) {
375
			$entityManager->addSetup('?->bindEntityManager(?)', [$this->prefix('@' . $name . '.diagnosticsPanel'), '@self']);
376
		}
377
378
		$builder->addDefinition($this->prefix('repositoryFactory.' . $name . '.defaultRepositoryFactory'))
379
				->setClass($config['defaultRepositoryClassName'])
380
				->setImplement('Kdyby\Doctrine\DI\IRepositoryFactory')
381
				->setArguments([new Code\PhpLiteral('$entityManager'), new Code\PhpLiteral('$classMetadata')])
382
				->setParameters(['Doctrine\ORM\EntityManagerInterface entityManager', 'Doctrine\ORM\Mapping\ClassMetadata classMetadata'])
383
				->setAutowired(FALSE);
384
385
		$builder->addDefinition($this->prefix($name . '.schemaValidator'))
386
			->setClass('Doctrine\ORM\Tools\SchemaValidator', ['@' . $managerServiceId])
387
			->setAutowired($isDefault);
388
389
		$builder->addDefinition($this->prefix($name . '.schemaTool'))
390
			->setClass('Doctrine\ORM\Tools\SchemaTool', ['@' . $managerServiceId])
391
			->setAutowired($isDefault);
392
393
		$cacheCleaner = $builder->addDefinition($this->prefix($name . '.cacheCleaner'))
394
			->setClass('Kdyby\Doctrine\Tools\CacheCleaner', ['@' . $managerServiceId])
395
			->setAutowired($isDefault);
396
397
		$builder->addDefinition($this->prefix($name . '.schemaManager'))
398
			->setClass('Doctrine\DBAL\Schema\AbstractSchemaManager')
399
			->setFactory('@Kdyby\Doctrine\Connection::getSchemaManager')
400
			->setAutowired($isDefault);
401
402
		foreach ($this->compiler->getExtensions('Kdyby\Annotations\DI\AnnotationsExtension') as $extension) {
403
			/** @var Kdyby\Annotations\DI\AnnotationsExtension $extension */
404
			$cacheCleaner->addSetup('addCacheStorage', [$extension->prefix('@cache.annotations')]);
405
		}
406
407
		if ($isDefault) {
408
			$builder->addDefinition($this->prefix('helper.entityManager'))
409
				->setClass('Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper', ['@' . $managerServiceId])
410
				->addTag(Kdyby\Console\DI\ConsoleExtension::HELPER_TAG, 'em');
411
412
			$builder->addDefinition($this->prefix('helper.connection'))
413
				->setClass('Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper', [$connectionService])
414
				->addTag(Kdyby\Console\DI\ConsoleExtension::HELPER_TAG, 'db');
415
416
			$builder->addAlias($this->prefix('schemaValidator'), $this->prefix($name . '.schemaValidator'));
417
			$builder->addAlias($this->prefix('schemaTool'), $this->prefix($name . '.schemaTool'));
418
			$builder->addAlias($this->prefix('cacheCleaner'), $this->prefix($name . '.cacheCleaner'));
419
			$builder->addAlias($this->prefix('schemaManager'), $this->prefix($name . '.schemaManager'));
420
		}
421
422
		$this->configuredManagers[$name] = $managerServiceId;
423
		$this->managerConfigs[$name] = $config;
424
	}
425
426
427
428
	protected function processSecondLevelCache($name, array $config, $isDefault)
429
	{
430
		if (!$config['enabled']) {
431
			return;
432
		}
433
434
		$builder = $this->getContainerBuilder();
435
436
		$cacheFactory = $builder->addDefinition($this->prefix($name . '.cacheFactory'))
437
			->setClass('Doctrine\ORM\Cache\CacheFactory')
438
			->setFactory($config['factoryClass'], [
439
				$this->prefix('@' . $name . '.cacheRegionsConfiguration'),
440
				$this->processCache($config['driver'], $name . '.secondLevel'),
441
			])
442
			->setAutowired($isDefault);
443
444
		if ($config['factoryClass'] === $this->managerDefaults['secondLevelCache']['factoryClass']
445
			|| is_subclass_of($config['factoryClass'], $this->managerDefaults['secondLevelCache']['factoryClass'])
446
		) {
447
			$cacheFactory->addSetup('setFileLockRegionDirectory', [$config['fileLockRegionDirectory']]);
448
		}
449
450
		$builder->addDefinition($this->prefix($name . '.cacheRegionsConfiguration'))
451
			->setClass('Doctrine\ORM\Cache\RegionsConfiguration', [
452
				$config['regions']['defaultLifetime'],
453
				$config['regions']['defaultLockLifetime'],
454
			])
455
			->setAutowired($isDefault);
456
457
		$logger = $builder->addDefinition($this->prefix($name . '.cacheLogger'))
458
			->setClass('Doctrine\ORM\Cache\Logging\CacheLogger')
459
			->setFactory('Doctrine\ORM\Cache\Logging\CacheLoggerChain')
460
			->setAutowired(FALSE);
461
462
		if ($config['logging']) {
463
			$logger->addSetup('setLogger', [
464
				'statistics',
465
				new Statement('Doctrine\ORM\Cache\Logging\StatisticsCacheLogger')
466
			]);
467
		}
468
469
		$builder->addDefinition($cacheConfigName = $this->prefix($name . '.ormCacheConfiguration'))
470
			->setClass('Doctrine\ORM\Cache\CacheConfiguration')
471
			->addSetup('setCacheFactory', [$this->prefix('@' . $name . '.cacheFactory')])
472
			->addSetup('setCacheLogger', [$this->prefix('@' . $name . '.cacheLogger')])
473
			->setAutowired($isDefault);
474
475
		$configuration = $builder->getDefinition($this->prefix($name . '.ormConfiguration'));
476
		$configuration->addSetup('setSecondLevelCacheEnabled');
477
		$configuration->addSetup('setSecondLevelCacheConfiguration', ['@' . $cacheConfigName]);
478
	}
479
480
481
482
	protected function processConnection($name, array $defaults, $isDefault = FALSE)
483
	{
484
		$builder = $this->getContainerBuilder();
485
		$config = $this->resolveConfig($defaults, $this->connectionDefaults, $this->managerDefaults);
486
487
		if ($isDefault) {
488
			$builder->parameters[$this->name]['dbal']['defaultConnection'] = $name;
489
		}
490
491
		if (isset($defaults['connection'])) {
492
			return $this->prefix('@' . $defaults['connection'] . '.connection');
493
		}
494
495
		// config
496
		$configuration = $builder->addDefinition($this->prefix($name . '.dbalConfiguration'))
497
			->setClass('Doctrine\DBAL\Configuration')
498
			->addSetup('setResultCacheImpl', [$this->processCache($config['resultCache'], $name . '.dbalResult')])
499
			->addSetup('setSQLLogger', [new Statement('Doctrine\DBAL\Logging\LoggerChain')])
500
			->addSetup('setFilterSchemaAssetsExpression', [$config['schemaFilter']])
501
			->setAutowired(FALSE);
502
503
		// types
504
		Validators::assertField($config, 'types', 'array');
505
		$schemaTypes = $dbalTypes = [];
506
		foreach ($config['types'] as $dbType => $className) {
507
			$typeInst = Code\Helpers::createObject($className, []);
508
			/** @var Doctrine\DBAL\Types\Type $typeInst */
509
			$dbalTypes[$typeInst->getName()] = $className;
510
			$schemaTypes[$dbType] = $typeInst->getName();
511
		}
512
513
		// tracy panel
514
		if ($this->isTracyPresent()) {
515
			$builder->addDefinition($this->prefix($name . '.diagnosticsPanel'))
516
				->setClass('Kdyby\Doctrine\Diagnostics\Panel')
517
				->setAutowired(FALSE);
518
		}
519
520
		// connection
521
		$options = array_diff_key($config, array_flip(['types', 'resultCache', 'connection', 'logging']));
522
		$connection = $builder->addDefinition($connectionServiceId = $this->prefix($name . '.connection'))
523
			->setClass('Kdyby\Doctrine\Connection')
524
			->setFactory('Kdyby\Doctrine\Connection::create', [
525
				$options,
526
				$this->prefix('@' . $name . '.dbalConfiguration'),
527
				$this->prefix('@' . $name . '.evm'),
528
			])
529
			->addSetup('setSchemaTypes', [$schemaTypes])
530
			->addSetup('setDbalTypes', [$dbalTypes])
531
			->addTag(self::TAG_CONNECTION)
532
			->addTag('kdyby.doctrine.connection')
533
			->setAutowired($isDefault);
534
535
		if ($this->isTracyPresent()) {
536
			$connection->addSetup('$panel = ?->bindConnection(?)', [$this->prefix('@' . $name . '.diagnosticsPanel'), '@self']);
537
		}
538
539
		/** @var Nette\DI\ServiceDefinition $connection */
540
541
		$this->configuredConnections[$name] = $connectionServiceId;
542
543
		if (!is_bool($config['logging'])) {
544
			$fileLogger = new Statement('Kdyby\Doctrine\Diagnostics\FileLogger', [$builder->expand($config['logging'])]);
545
			$configuration->addSetup('$service->getSQLLogger()->addLogger(?)', [$fileLogger]);
546
547
		} elseif ($config['logging']) {
548
			$connection->addSetup('?->enableLogging()', [new Code\PhpLiteral('$panel')]);
549
		}
550
551
		return $this->prefix('@' . $name . '.connection');
552
	}
553
554
555
556
	/**
557
	 * @param \Nette\DI\ServiceDefinition $metadataDriver
558
	 * @param string $namespace
559
	 * @param string|object $driver
560
	 * @param string $prefix
561
	 * @throws \Nette\Utils\AssertionException
562
	 * @return string
563
	 */
564
	protected function processMetadataDriver(Nette\DI\ServiceDefinition $metadataDriver, $namespace, $driver, $prefix)
565
	{
566
		if (!is_string($namespace) || !Strings::match($namespace, '#^' . self::PHP_NAMESPACE . '\z#')) {
567
			throw new Nette\Utils\AssertionException("The metadata namespace expects to be valid namespace, $namespace given.");
568
		}
569
		$namespace = ltrim($namespace, '\\');
570
571
		if (is_string($driver) && strpos($driver, '@') === 0) { // service reference
572
			$metadataDriver->addSetup('addDriver', [$driver, $namespace]);
573
			return $driver;
574
		}
575
576
		if (is_string($driver) || is_array($driver)) {
577
			$paths = is_array($driver) ? $driver : [$driver];
578
			foreach ($paths as $path) {
579
				if (($pos = strrpos($path, '*')) !== FALSE) {
580
					$path = substr($path, 0, $pos);
581
				}
582
583
				if (!file_exists($path)) {
584
					throw new Nette\Utils\AssertionException("The metadata path expects to be an existing directory, $path given.");
585
				}
586
			}
587
588
			$driver = new Statement(self::ANNOTATION_DRIVER, is_array($paths) ? $paths : [$paths]);
589
		}
590
591
		$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...
592
		list($driver) = CacheHelpers::filterArgs($driver);
593
		/** @var Statement $driver */
594
595
		/** @var string $impl */
596
		if (isset($this->metadataDriverClasses[$impl])) {
597
			$driver = new Statement($this->metadataDriverClasses[$impl], $driver->arguments);
598
		}
599
600
		if (is_string($driver->getEntity()) && substr($driver->getEntity(), 0, 1) === '@') {
601
			$metadataDriver->addSetup('addDriver', [$driver->getEntity(), $namespace]);
602
			return $driver->getEntity();
603
		}
604
605
		if ($impl === self::ANNOTATION_DRIVER) {
606
			$driver->arguments = [
607
				Nette\Utils\Arrays::flatten($driver->arguments),
608
				2 => $this->prefix('@cache.' . $prefix . '.metadata')
609
			];
610
		}
611
612
		$serviceName = $this->prefix($prefix . '.driver.' . str_replace('\\', '_', $namespace) . '.' . str_replace('\\', '_', $impl) . 'Impl');
613
614
		$this->getContainerBuilder()->addDefinition($serviceName)
615
			->setClass('Doctrine\Common\Persistence\Mapping\Driver\MappingDriver')
616
			->setFactory($driver->getEntity(), $driver->arguments)
617
			->setAutowired(FALSE);
618
619
		$metadataDriver->addSetup('addDriver', ['@' . $serviceName, $namespace]);
620
		return '@' . $serviceName;
621
	}
622
623
624
625
	/**
626
	 * @param string|\stdClass $cache
627
	 * @param string $suffix
628
	 * @return string
629
	 */
630
	protected function processCache($cache, $suffix)
631
	{
632
		return CacheHelpers::processCache($this, $cache, $suffix, $this->getContainerBuilder()->parameters[$this->prefix('debug')]);
633
	}
634
635
636
637
	public function beforeCompile()
638
	{
639
		$this->processRepositories();
640
		$this->processEventManagers();
641
	}
642
643
644
645
	protected function processRepositories()
646
	{
647
		$builder = $this->getContainerBuilder();
648
649
		$disabled = TRUE;
650
		foreach ($this->configuredManagers as $managerName => $_) {
651
			$factoryClassName = $builder->getDefinition($this->prefix($managerName . '.repositoryFactory'))->getClass();
652
			if ($factoryClassName === 'Kdyby\Doctrine\RepositoryFactory' || in_array('Kdyby\Doctrine\RepositoryFactory', class_parents($factoryClassName), TRUE)) {
653
				$disabled = FALSE;
654
			}
655
		}
656
657
		if ($disabled) {
658
			return;
659
		}
660
661
		if (!method_exists($builder, 'findByType')) {
662
			foreach ($this->configuredManagers as $managerName => $_) {
663
				$builder->getDefinition($this->prefix($managerName . '.repositoryFactory'))
664
					->addSetup('setServiceIdsMap', [[], $this->prefix('repositoryFactory.' . $managerName . '.defaultRepositoryFactory')]);
665
			}
666
667
			return;
668
		}
669
670
		$serviceMap = array_fill_keys(array_keys($this->configuredManagers), []);
671
		foreach ($builder->findByType('Doctrine\ORM\EntityRepository', FALSE) as $originalServiceName => $originalDef) {
672
			if (is_string($originalDef)) {
673
				$originalServiceName = $originalDef;
674
				$originalDef = $builder->getDefinition($originalServiceName);
675
			}
676
677
			if (strpos($originalServiceName, $this->name . '.') === 0) {
678
				continue; // ignore
679
			}
680
681
			$factory = $originalDef->getFactory() ? $originalDef->getFactory()->getEntity() : $originalDef->getClass();
682
			if (stripos($factory, '::getRepository') !== FALSE) {
683
				continue; // ignore
684
			}
685
686
			$factoryServiceName = $this->prefix('repositoryFactory.' . $originalServiceName);
687
			$factoryDef = $builder->addDefinition($factoryServiceName, $originalDef)
688
				->setImplement('Kdyby\Doctrine\DI\IRepositoryFactory')
689
				->setParameters(['Doctrine\ORM\EntityManagerInterface entityManager', 'Doctrine\ORM\Mapping\ClassMetadata classMetadata'])
690
				->setAutowired(FALSE);
691
			$factoryStatement = $factoryDef->getFactory() ?: new Statement($factoryDef->getClass());
692
			$factoryStatement->arguments[0] = new Code\PhpLiteral('$entityManager');
693
			$factoryStatement->arguments[1] = new Code\PhpLiteral('$classMetadata');
694
			$factoryDef->setArguments($factoryStatement->arguments);
695
696
			$boundManagers = $this->getServiceBoundManagers($originalDef);
697
			Validators::assert($boundManagers, 'list:1', 'bound manager');
698
699
			if ($boundEntity = $originalDef->getTag(self::TAG_REPOSITORY_ENTITY)) {
700
				if (!is_string($boundEntity) || !class_exists($boundEntity)) {
701
					throw new Nette\Utils\AssertionException(sprintf('The entity "%s" for repository "%s" cannot be autoloaded.', $boundEntity, $originalDef->getClass()));
702
				}
703
				$entityArgument = $boundEntity;
704
705
			} else {
706
				throw new Nette\Utils\AssertionException(sprintf(
707
					'The magic auto-detection of entity for repository %s for IRepositoryFactory was removed from Kdyby.' .
708
					'You have to specify %s tag with classname of the related entity in order to use this feature.',
709
					$originalDef->getClass(),
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');
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('Kdyby\Doctrine\Diagnostics\Panel::registerBluescreen($this);');
768
			$this->addCollapsePathsToTracy($init);
769
		}
770
771
		foreach ($this->proxyAutoloaders as $namespace => $dir) {
772
			$originalInitialize = $init->getBody();
773
			$init->setBody('Kdyby\Doctrine\Proxy\ProxyAutoloader::create(?, ?)->register();', [$dir, $namespace]);
774
			$init->addBody($originalInitialize);
775
		}
776
	}
777
778
779
780
	/**
781
	 * @param $provided
782
	 * @param $defaults
783
	 * @param $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');
829
	}
830
831
832
833
	/**
834
	 * @return bool
835
	 */
836
	private function isKdybyEventsPresent()
837
	{
838
		return (bool) $this->compiler->getExtensions('Kdyby\Events\DI\EventsExtension');
839
	}
840
841
842
843
	private function addCollapsePathsToTracy(Method $init)
844
	{
845
		$blueScreen = 'Tracy\Debugger::getBlueScreen()';
846
		$commonDirname = dirname(Nette\Reflection\ClassType::from('Doctrine\Common\Version')->getFileName());
847
848
		$init->addBody($blueScreen . '->collapsePaths[] = ?;', [dirname(Nette\Reflection\ClassType::from('Kdyby\Doctrine\Exception')->getFileName())]);
849
		$init->addBody($blueScreen . '->collapsePaths[] = ?;', [dirname(dirname(dirname(dirname($commonDirname))))]); // this should be vendor/doctrine
850
		foreach ($this->proxyAutoloaders as $dir) {
851
			$init->addBody($blueScreen . '->collapsePaths[] = ?;', [$dir]);
852
		}
853
	}
854
855
856
857
	/**
858
	 * @param \Nette\Configurator $configurator
859
	 */
860
	public static function register(Nette\Configurator $configurator)
861
	{
862
		$configurator->onCompile[] = function ($config, Nette\DI\Compiler $compiler) {
863
			$compiler->addExtension('doctrine', new OrmExtension());
864
		};
865
	}
866
867
868
869
	/**
870
	 * @param array $array
871
	 */
872
	private static function natSortKeys(array &$array)
873
	{
874
		$keys = array_keys($array);
875
		natsort($keys);
876
		$keys = array_flip(array_reverse($keys, TRUE));
877
		$array = array_merge($keys, $array);
878
		return $array;
879
	}
880
881
}
882