Completed
Pull Request — master (#284)
by
unknown
02:42
created

OrmExtension::processRepositoryFactoryEntities()   C

Complexity

Conditions 8
Paths 11

Size

Total Lines 38
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

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

1 Method

Rating   Name   Duplication   Size   Complexity  
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);
0 ignored issues
show
Bug introduced by
It seems like $emConfig defined by \Nette\DI\Config\Helpers...e($emConfig, $defaults) on line 176 can also be of type string; however, Kdyby\Doctrine\DI\OrmExt...:processEntityManager() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
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::SUBSCRIBER_TAG)
0 ignored issues
show
Deprecated Code introduced by
The constant Kdyby\Events\DI\EventsExtension::SUBSCRIBER_TAG has been deprecated.

This class constant has been deprecated.

Loading history...
188
				->setInject(FALSE);
189
190
			foreach ($this->targetEntityMappings as $originalEntity => $mapping) {
191
				$listener->addSetup('addResolveTargetEntity', [$originalEntity, $mapping['targetEntity'], $mapping]);
192
			}
193
		}
194
195
		$this->loadConsole();
196
197
		$builder->addDefinition($this->prefix('registry'))
198
			->setClass('Kdyby\Doctrine\Registry', [
199
				$this->configuredConnections,
200
				$this->configuredManagers,
201
				$builder->parameters[$this->name]['dbal']['defaultConnection'],
202
				$builder->parameters[$this->name]['orm']['defaultEntityManager'],
203
			]);
204
	}
205
206
207
208
	protected function loadConsole()
209
	{
210
		$builder = $this->getContainerBuilder();
211
212
		foreach ($this->loadFromFile(__DIR__ . '/console.neon') as $i => $command) {
0 ignored issues
show
Bug introduced by
The expression $this->loadFromFile(__DIR__ . '/console.neon') of type array|string is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
213
			$cli = $builder->addDefinition($this->prefix('cli.' . $i))
214
				->addTag(Kdyby\Console\DI\ConsoleExtension::COMMAND_TAG)
0 ignored issues
show
Deprecated Code introduced by
The constant Kdyby\Console\DI\ConsoleExtension::COMMAND_TAG has been deprecated.

This class constant has been deprecated.

Loading history...
215
				->setInject(FALSE); // lazy injects
216
217
			if (is_string($command)) {
218
				$cli->setClass($command);
219
220
			} else {
221
				throw new Kdyby\Doctrine\NotSupportedException;
222
			}
223
		}
224
	}
225
226
227
228
	protected function processEntityManager($name, array $defaults)
229
	{
230
		$builder = $this->getContainerBuilder();
231
		$config = $this->resolveConfig($defaults, $this->managerDefaults, $this->connectionDefaults);
232
233
		if ($isDefault = !isset($builder->parameters[$this->name]['orm']['defaultEntityManager'])) {
234
			$builder->parameters[$this->name]['orm']['defaultEntityManager'] = $name;
235
		}
236
237
		$metadataDriver = $builder->addDefinition($this->prefix($name . '.metadataDriver'))
238
			->setClass('Doctrine\Common\Persistence\Mapping\Driver\MappingDriverChain')
239
			->setAutowired(FALSE)
240
			->setInject(FALSE);
241
		/** @var Nette\DI\ServiceDefinition $metadataDriver */
242
243
		Validators::assertField($config, 'metadata', 'array');
244
		Validators::assertField($config, 'targetEntityMappings', 'array');
245
		$config['targetEntityMappings'] = $this->normalizeTargetEntityMappings($config['targetEntityMappings']);
246
		foreach ($this->compiler->getExtensions() as $extension) {
247
			if ($extension instanceof IEntityProvider) {
248
				$metadata = $extension->getEntityMappings();
249
				Validators::assert($metadata, 'array');
250
				$config['metadata'] = array_merge($config['metadata'], $metadata);
251
			}
252
253
			if ($extension instanceof ITargetEntityProvider) {
254
				$targetEntities = $extension->getTargetEntityMappings();
255
				Validators::assert($targetEntities, 'array');
256
				$config['targetEntityMappings'] = Nette\Utils\Arrays::mergeTree($config['targetEntityMappings'], $this->normalizeTargetEntityMappings($targetEntities));
257
			}
258
259
			if ($extension instanceof IDatabaseTypeProvider) {
260
				$providedTypes = $extension->getDatabaseTypes();
261
				Validators::assert($providedTypes, 'array');
262
263
				if (!isset($defaults['types'])) {
264
					$defaults['types'] = [];
265
				}
266
267
				$defaults['types'] = array_merge($defaults['types'], $providedTypes);
268
			}
269
		}
270
271
		foreach (self::natSortKeys($config['metadata']) as $namespace => $driver) {
272
			$this->processMetadataDriver($metadataDriver, $namespace, $driver, $name);
273
		}
274
275
		$this->processMetadataDriver($metadataDriver, 'Kdyby\\Doctrine', __DIR__ . '/../Entities', $name);
276
277
		if (empty($config['metadata'])) {
278
			$metadataDriver->addSetup('setDefaultDriver', [
279
				new Statement($this->metadataDriverClasses[self::ANNOTATION_DRIVER], [
280
					[$builder->expand('%appDir%')],
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ContainerBuilder::expand() has been deprecated.

This method has been deprecated.

Loading history...
281
					2 => $this->prefix('@cache.' . $name . '.metadata')
282
				])
283
			]);
284
		}
285
286
		if ($config['repositoryFactoryClassName'] === 'default') {
287
			$config['repositoryFactoryClassName'] = 'Doctrine\ORM\Repository\DefaultRepositoryFactory';
288
		}
289
		$builder->addDefinition($this->prefix($name . '.repositoryFactory'))
290
			->setClass($config['repositoryFactoryClassName'])
291
			->setAutowired(FALSE);
292
293
		Validators::assertField($config, 'namespaceAlias', 'array');
294
		Validators::assertField($config, 'hydrators', 'array');
295
		Validators::assertField($config, 'dql', 'array');
296
		Validators::assertField($config['dql'], 'string', 'array');
297
		Validators::assertField($config['dql'], 'numeric', 'array');
298
		Validators::assertField($config['dql'], 'datetime', 'array');
299
		Validators::assertField($config['dql'], 'hints', 'array');
300
301
		$autoGenerateProxyClasses = is_bool($config['autoGenerateProxyClasses'])
302
			? ($config['autoGenerateProxyClasses'] ? AbstractProxyFactory::AUTOGENERATE_ALWAYS : AbstractProxyFactory::AUTOGENERATE_NEVER)
303
			: $config['autoGenerateProxyClasses'];
304
305
		$configuration = $builder->addDefinition($this->prefix($name . '.ormConfiguration'))
306
			->setClass('Kdyby\Doctrine\Configuration')
307
			->addSetup('setMetadataCacheImpl', [$this->processCache($config['metadataCache'], $name . '.metadata')])
308
			->addSetup('setQueryCacheImpl', [$this->processCache($config['queryCache'], $name . '.query')])
309
			->addSetup('setResultCacheImpl', [$this->processCache($config['resultCache'], $name . '.ormResult')])
310
			->addSetup('setHydrationCacheImpl', [$this->processCache($config['hydrationCache'], $name . '.hydration')])
311
			->addSetup('setMetadataDriverImpl', [$this->prefix('@' . $name . '.metadataDriver')])
312
			->addSetup('setClassMetadataFactoryName', [$config['classMetadataFactory']])
313
			->addSetup('setDefaultRepositoryClassName', [$config['defaultRepositoryClassName']])
314
			->addSetup('setQueryBuilderClassName', [$config['queryBuilderClassName']])
315
			->addSetup('setRepositoryFactory', [$this->prefix('@' . $name . '.repositoryFactory')])
316
			->addSetup('setProxyDir', [$config['proxyDir']])
317
			->addSetup('setProxyNamespace', [$config['proxyNamespace']])
318
			->addSetup('setAutoGenerateProxyClasses', [$autoGenerateProxyClasses])
319
			->addSetup('setEntityNamespaces', [$config['namespaceAlias']])
320
			->addSetup('setCustomHydrationModes', [$config['hydrators']])
321
			->addSetup('setCustomStringFunctions', [$config['dql']['string']])
322
			->addSetup('setCustomNumericFunctions', [$config['dql']['numeric']])
323
			->addSetup('setCustomDatetimeFunctions', [$config['dql']['datetime']])
324
			->addSetup('setDefaultQueryHints', [$config['dql']['hints']])
325
			->addSetup('setNamingStrategy', CacheHelpers::filterArgs($config['namingStrategy']))
326
			->addSetup('setQuoteStrategy', CacheHelpers::filterArgs($config['quoteStrategy']))
327
			->addSetup('setEntityListenerResolver', CacheHelpers::filterArgs($config['entityListenerResolver']))
328
			->setAutowired(FALSE)
329
			->setInject(FALSE);
330
		/** @var Nette\DI\ServiceDefinition $configuration */
331
332
		$this->proxyAutoloaders[$config['proxyNamespace']] = $config['proxyDir'];
333
334
		$this->processSecondLevelCache($name, $config['secondLevelCache'], $isDefault);
335
336
		Validators::assertField($config, 'filters', 'array');
337
		foreach ($config['filters'] as $filterName => $filterClass) {
338
			$configuration->addSetup('addFilter', [$filterName, $filterClass]);
339
		}
340
341
		if ($config['targetEntityMappings']) {
342
			$configuration->addSetup('setTargetEntityMap', [array_map(function ($mapping) {
343
				return $mapping['targetEntity'];
344
			}, $config['targetEntityMappings'])]);
345
			$this->targetEntityMappings = Nette\Utils\Arrays::mergeTree($this->targetEntityMappings, $config['targetEntityMappings']);
346
		}
347
348
		if ($this->isKdybyEventsPresent()) {
349
			$builder->addDefinition($this->prefix($name . '.evm'))
350
				->setClass('Kdyby\Events\NamespacedEventManager', [Kdyby\Doctrine\Events::NS . '::'])
351
				->addSetup('$dispatchGlobalEvents', [TRUE]) // for BC
352
				->setAutowired(FALSE);
353
354
		} else {
355
			$builder->addDefinition($this->prefix($name . '.evm'))
356
				->setClass('Doctrine\Common\EventManager')
357
				->setAutowired(FALSE);
358
		}
359
360
		// entity manager
361
		$entityManager = $builder->addDefinition($managerServiceId = $this->prefix($name . '.entityManager'))
362
			->setClass('Kdyby\Doctrine\EntityManager')
363
			->setFactory('Kdyby\Doctrine\EntityManager::create', [
364
				$connectionService = $this->processConnection($name, $defaults, $isDefault),
365
				$this->prefix('@' . $name . '.ormConfiguration'),
366
				$this->prefix('@' . $name . '.evm'),
367
			])
368
			->addTag(self::TAG_ENTITY_MANAGER)
369
			->addTag('kdyby.doctrine.entityManager')
370
			->setAutowired($isDefault)
371
			->setInject(FALSE);
372
373
		if ($this->isTracyPresent()) {
374
			$entityManager->addSetup('?->bindEntityManager(?)', [$this->prefix('@' . $name . '.diagnosticsPanel'), '@self']);
375
		}
376
377
		$builder->addDefinition($this->prefix('repositoryFactory.' . $name . '.defaultRepositoryFactory'))
378
				->setClass($config['defaultRepositoryClassName'])
379
				->setImplement('Kdyby\Doctrine\DI\IRepositoryFactory')
380
				->setArguments([new Code\PhpLiteral('$entityManager'), new Code\PhpLiteral('$classMetadata')])
381
				->setParameters(['Doctrine\ORM\EntityManagerInterface entityManager', 'Doctrine\ORM\Mapping\ClassMetadata classMetadata'])
382
				->setAutowired(FALSE);
383
384
		$builder->addDefinition($this->prefix($name . '.schemaValidator'))
385
			->setClass('Doctrine\ORM\Tools\SchemaValidator', ['@' . $managerServiceId])
386
			->setAutowired($isDefault);
387
388
		$builder->addDefinition($this->prefix($name . '.schemaTool'))
389
			->setClass('Doctrine\ORM\Tools\SchemaTool', ['@' . $managerServiceId])
390
			->setAutowired($isDefault);
391
392
		$cacheCleaner = $builder->addDefinition($this->prefix($name . '.cacheCleaner'))
393
			->setClass('Kdyby\Doctrine\Tools\CacheCleaner', ['@' . $managerServiceId])
394
			->setAutowired($isDefault);
395
396
		$builder->addDefinition($this->prefix($name . '.schemaManager'))
397
			->setClass('Doctrine\DBAL\Schema\AbstractSchemaManager')
398
			->setFactory('@Kdyby\Doctrine\Connection::getSchemaManager')
399
			->setAutowired($isDefault);
400
401
		foreach ($this->compiler->getExtensions('Kdyby\Annotations\DI\AnnotationsExtension') as $extension) {
402
			/** @var Kdyby\Annotations\DI\AnnotationsExtension $extension */
403
			$cacheCleaner->addSetup('addCacheStorage', [$extension->prefix('@cache.annotations')]);
404
		}
405
406
		if ($isDefault) {
407
			$builder->addDefinition($this->prefix('helper.entityManager'))
408
				->setClass('Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper', ['@' . $managerServiceId])
409
				->addTag(Kdyby\Console\DI\ConsoleExtension::HELPER_TAG, 'em');
0 ignored issues
show
Documentation introduced by
'em' is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Deprecated Code introduced by
The constant Kdyby\Console\DI\ConsoleExtension::HELPER_TAG has been deprecated.

This class constant has been deprecated.

Loading history...
410
411
			$builder->addDefinition($this->prefix('helper.connection'))
412
				->setClass('Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper', [$connectionService])
413
				->addTag(Kdyby\Console\DI\ConsoleExtension::HELPER_TAG, 'db');
0 ignored issues
show
Documentation introduced by
'db' is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Deprecated Code introduced by
The constant Kdyby\Console\DI\ConsoleExtension::HELPER_TAG has been deprecated.

This class constant has been deprecated.

Loading history...
414
415
			$builder->addAlias($this->prefix('schemaValidator'), $this->prefix($name . '.schemaValidator'));
416
			$builder->addAlias($this->prefix('schemaTool'), $this->prefix($name . '.schemaTool'));
417
			$builder->addAlias($this->prefix('cacheCleaner'), $this->prefix($name . '.cacheCleaner'));
418
			$builder->addAlias($this->prefix('schemaManager'), $this->prefix($name . '.schemaManager'));
419
		}
420
421
		$this->configuredManagers[$name] = $managerServiceId;
422
		$this->managerConfigs[$name] = $config;
423
	}
424
425
426
427
	protected function processSecondLevelCache($name, array $config, $isDefault)
428
	{
429
		if (!$config['enabled']) {
430
			return;
431
		}
432
433
		$builder = $this->getContainerBuilder();
434
435
		$cacheFactory = $builder->addDefinition($this->prefix($name . '.cacheFactory'))
436
			->setClass('Doctrine\ORM\Cache\CacheFactory')
437
			->setFactory($config['factoryClass'], [
438
				$this->prefix('@' . $name . '.cacheRegionsConfiguration'),
439
				$this->processCache($config['driver'], $name . '.secondLevel'),
440
			])
441
			->setAutowired($isDefault);
442
443
		if ($config['factoryClass'] === $this->managerDefaults['secondLevelCache']['factoryClass']
444
			|| is_subclass_of($config['factoryClass'], $this->managerDefaults['secondLevelCache']['factoryClass'])
445
		) {
446
			$cacheFactory->addSetup('setFileLockRegionDirectory', [$config['fileLockRegionDirectory']]);
447
		}
448
449
		$builder->addDefinition($this->prefix($name . '.cacheRegionsConfiguration'))
450
			->setClass('Doctrine\ORM\Cache\RegionsConfiguration', [
451
				$config['regions']['defaultLifetime'],
452
				$config['regions']['defaultLockLifetime'],
453
			])
454
			->setAutowired($isDefault);
455
456
		$logger = $builder->addDefinition($this->prefix($name . '.cacheLogger'))
457
			->setClass('Doctrine\ORM\Cache\Logging\CacheLogger')
458
			->setFactory('Doctrine\ORM\Cache\Logging\CacheLoggerChain')
459
			->setAutowired(FALSE);
460
461
		if ($config['logging']) {
462
			$logger->addSetup('setLogger', [
463
				'statistics',
464
				new Statement('Doctrine\ORM\Cache\Logging\StatisticsCacheLogger')
465
			]);
466
		}
467
468
		$builder->addDefinition($cacheConfigName = $this->prefix($name . '.ormCacheConfiguration'))
469
			->setClass('Doctrine\ORM\Cache\CacheConfiguration')
470
			->addSetup('setCacheFactory', [$this->prefix('@' . $name . '.cacheFactory')])
471
			->addSetup('setCacheLogger', [$this->prefix('@' . $name . '.cacheLogger')])
472
			->setAutowired($isDefault);
473
474
		$configuration = $builder->getDefinition($this->prefix($name . '.ormConfiguration'));
475
		$configuration->addSetup('setSecondLevelCacheEnabled');
476
		$configuration->addSetup('setSecondLevelCacheConfiguration', ['@' . $cacheConfigName]);
477
	}
478
479
480
481
	protected function processConnection($name, array $defaults, $isDefault = FALSE)
482
	{
483
		$builder = $this->getContainerBuilder();
484
		$config = $this->resolveConfig($defaults, $this->connectionDefaults, $this->managerDefaults);
485
486
		if ($isDefault) {
487
			$builder->parameters[$this->name]['dbal']['defaultConnection'] = $name;
488
		}
489
490
		if (isset($defaults['connection'])) {
491
			return $this->prefix('@' . $defaults['connection'] . '.connection');
492
		}
493
494
		// config
495
		$configuration = $builder->addDefinition($this->prefix($name . '.dbalConfiguration'))
496
			->setClass('Doctrine\DBAL\Configuration')
497
			->addSetup('setResultCacheImpl', [$this->processCache($config['resultCache'], $name . '.dbalResult')])
498
			->addSetup('setSQLLogger', [new Statement('Doctrine\DBAL\Logging\LoggerChain')])
499
			->addSetup('setFilterSchemaAssetsExpression', [$config['schemaFilter']])
500
			->setAutowired(FALSE)
501
			->setInject(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
			->setInject(FALSE);
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'])]);
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ContainerBuilder::expand() has been deprecated.

This method has been deprecated.

Loading history...
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);
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
			->setInject(FALSE);
615
616
		$metadataDriver->addSetup('addDriver', ['@' . $serviceName, $namespace]);
617
		return '@' . $serviceName;
618
	}
619
620
621
622
	/**
623
	 * @param string|\stdClass $cache
624
	 * @param string $suffix
625
	 * @return string
626
	 */
627
	protected function processCache($cache, $suffix)
628
	{
629
		return CacheHelpers::processCache($this, $cache, $suffix, $this->getContainerBuilder()->parameters[$this->prefix('debug')]);
630
	}
631
632
633
634
	public function beforeCompile()
635
	{
636
		$this->processRepositories();
637
		$this->processEventManagers();
638
	}
639
640
641
642
	protected function processRepositories()
643
	{
644
		$builder = $this->getContainerBuilder();
645
646
		$disabled = TRUE;
647
		foreach ($this->configuredManagers as $managerName => $_) {
648
			$factoryClassName = $builder->getDefinition($this->prefix($managerName . '.repositoryFactory'))->getClass();
649
			if ($factoryClassName === 'Kdyby\Doctrine\RepositoryFactory' || in_array('Kdyby\Doctrine\RepositoryFactory', class_parents($factoryClassName), TRUE)) {
650
				$disabled = FALSE;
651
			}
652
		}
653
654
		if ($disabled) {
655
			return;
656
		}
657
658
		if (!method_exists($builder, 'findByType')) {
659
			foreach ($this->configuredManagers as $managerName => $_) {
660
				$builder->getDefinition($this->prefix($managerName . '.repositoryFactory'))
661
					->addSetup('setServiceIdsMap', [[], $this->prefix('repositoryFactory.' . $managerName . '.defaultRepositoryFactory')]);
662
			}
663
664
			return;
665
		}
666
667
		$serviceMap = array_fill_keys(array_keys($this->configuredManagers), []);
668
		foreach ($builder->findByType('Doctrine\ORM\EntityRepository', FALSE) as $originalServiceName => $originalDef) {
0 ignored issues
show
Unused Code introduced by
The call to ContainerBuilder::findByType() has too many arguments starting with FALSE.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
669
			if (is_string($originalDef)) {
670
				$originalServiceName = $originalDef;
671
				$originalDef = $builder->getDefinition($originalServiceName);
672
			}
673
674
			if (strpos($originalServiceName, $this->name . '.') === 0) {
675
				continue; // ignore
676
			}
677
678
			$factory = $originalDef->getFactory() ? $originalDef->getFactory()->getEntity() : $originalDef->getClass();
679
			if (stripos($factory, '::getRepository') !== FALSE) {
680
				continue; // ignore
681
			}
682
683
			$factoryServiceName = $this->prefix('repositoryFactory.' . $originalServiceName);
684
			$factoryDef = $builder->addDefinition($factoryServiceName, $originalDef)
685
				->setImplement('Kdyby\Doctrine\DI\IRepositoryFactory')
686
				->setParameters(['Doctrine\ORM\EntityManagerInterface entityManager', 'Doctrine\ORM\Mapping\ClassMetadata classMetadata'])
687
				->setAutowired(FALSE);
688
			$factoryStatement = $factoryDef->getFactory() ?: new Statement($factoryDef->getClass());
689
			$factoryStatement->arguments[0] = new Code\PhpLiteral('$entityManager');
690
			$factoryStatement->arguments[1] = new Code\PhpLiteral('$classMetadata');
691
			$factoryDef->setArguments($factoryStatement->arguments);
692
693
			$boundManagers = $this->getServiceBoundManagers($originalDef);
694
			Validators::assert($boundManagers, 'list:1', 'bound manager');
695
696
			if ($boundEntity = $originalDef->getTag(self::TAG_REPOSITORY_ENTITY)) {
697
				if (!is_string($boundEntity) || !class_exists($boundEntity)) {
698
					throw new Nette\Utils\AssertionException(sprintf('The entity "%s" for repository "%s" cannot be autoloaded.', $boundEntity, $originalDef->getClass()));
699
				}
700
				$entityArgument = $boundEntity;
701
702
			} else {
703
				// TODO Obtain an Entity FQN w/o ClassMetadataFactory::getAllMetadata invoking or should it be done another faster way?
704
				$entityArgument = new Code\PhpLiteral('"%entityName%"');
705
			}
706
707
			$builder->removeDefinition($originalServiceName);
708
			$builder->addDefinition($originalServiceName)
709
				->setClass($originalDef->getClass())
710
				->setFactory(sprintf('@%s::getRepository', $this->configuredManagers[$boundManagers[0]]), [$entityArgument]);
711
712
			$serviceMap[$boundManagers[0]][$originalDef->getClass()] = $factoryServiceName;
713
		}
714
715
		foreach ($this->configuredManagers as $managerName => $_) {
716
			$builder->getDefinition($this->prefix($managerName . '.repositoryFactory'))
717
				->addSetup('setServiceIdsMap', [
718
					$serviceMap[$managerName],
719
					$this->prefix('repositoryFactory.' . $managerName . '.defaultRepositoryFactory')
720
				]);
721
		}
722
	}
723
724
725
726
	protected function processEventManagers()
727
	{
728
		$builder = $this->getContainerBuilder();
729
		$customEvmService = $builder->getByType('Doctrine\Common\EventManager');
730
		if ($this->isKdybyEventsPresent() || !$customEvmService) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $customEvmService of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
731
			return;
732
		}
733
734
		foreach ($this->configuredManagers as $managerName => $_) {
735
			$builder->getDefinition($this->prefix($managerName . '.evm'))->setFactory('@' . $customEvmService);
736
		}
737
	}
738
739
740
741
	/**
742
	 * @param Nette\DI\ServiceDefinition $def
743
	 * @return string[]
744
	 */
745
	protected function getServiceBoundManagers(Nette\DI\ServiceDefinition $def)
746
	{
747
		$builder = $this->getContainerBuilder();
748
		$boundManagers = $def->getTag(self::TAG_BIND_TO_MANAGER);
749
750
		return is_array($boundManagers) ? $boundManagers : [$builder->parameters[$this->name]['orm']['defaultEntityManager']];
751
	}
752
753
754
755
	public function afterCompile(Code\ClassType $class)
756
	{
757
		$init = $class->getMethod('initialize');
758
759
		if ($this->isTracyPresent()) {
760
			$init->addBody('Kdyby\Doctrine\Diagnostics\Panel::registerBluescreen($this);');
761
			$this->addCollapsePathsToTracy($init);
762
		}
763
764
		foreach ($this->proxyAutoloaders as $namespace => $dir) {
765
			$originalInitialize = $init->getBody();
766
			$init->setBody('Kdyby\Doctrine\Proxy\ProxyAutoloader::create(?, ?)->register();', [$dir, $namespace]);
767
			$init->addBody($originalInitialize);
768
		}
769
	}
770
771
772
773
	/**
774
	 * @param $provided
775
	 * @param $defaults
776
	 * @param $diff
777
	 * @return array
778
	 */
779
	private function resolveConfig(array $provided, array $defaults, array $diff = [])
780
	{
781
		return $this->getContainerBuilder()->expand(Nette\DI\Config\Helpers::merge(
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ContainerBuilder::expand() has been deprecated.

This method has been deprecated.

Loading history...
782
			array_diff_key($provided, array_diff_key($diff, $defaults)),
783
			$defaults
784
		));
785
	}
786
787
788
	/**
789
	 * @param array $targetEntityMappings
790
	 * @return array
791
	 */
792
	private function normalizeTargetEntityMappings(array $targetEntityMappings)
793
	{
794
		$normalized = [];
795
		foreach ($targetEntityMappings as $originalEntity => $targetEntity) {
796
			$originalEntity = ltrim($originalEntity, '\\');
797
			Validators::assert($targetEntity, 'array|string');
798
			if (is_array($targetEntity)) {
799
				Validators::assertField($targetEntity, 'targetEntity', 'string');
800
				$mapping = array_merge($targetEntity, [
801
					'targetEntity' => ltrim($targetEntity['targetEntity'], '\\')
802
				]);
803
804
			} else {
805
				$mapping = [
806
					'targetEntity' => ltrim($targetEntity, '\\'),
807
				];
808
			}
809
			$normalized[$originalEntity] = $mapping;
810
		}
811
		return $normalized;
812
	}
813
814
815
816
	/**
817
	 * @return bool
818
	 */
819
	private function isTracyPresent()
820
	{
821
		return interface_exists('Tracy\IBarPanel');
822
	}
823
824
825
826
	/**
827
	 * @return bool
828
	 */
829
	private function isKdybyEventsPresent()
830
	{
831
		return (bool) $this->compiler->getExtensions('Kdyby\Events\DI\EventsExtension');
832
	}
833
834
835
836
	private function addCollapsePathsToTracy(Method $init)
837
	{
838
		$blueScreen = 'Tracy\Debugger::getBlueScreen()';
839
		$commonDirname = dirname(Nette\Reflection\ClassType::from('Doctrine\Common\Version')->getFileName());
840
841
		$init->addBody($blueScreen . '->collapsePaths[] = ?;', [dirname(Nette\Reflection\ClassType::from('Kdyby\Doctrine\Exception')->getFileName())]);
842
		$init->addBody($blueScreen . '->collapsePaths[] = ?;', [dirname(dirname(dirname(dirname($commonDirname))))]); // this should be vendor/doctrine
843
		foreach ($this->proxyAutoloaders as $dir) {
844
			$init->addBody($blueScreen . '->collapsePaths[] = ?;', [$dir]);
845
		}
846
	}
847
848
849
850
	/**
851
	 * @param \Nette\Configurator $configurator
852
	 */
853
	public static function register(Nette\Configurator $configurator)
854
	{
855
		$configurator->onCompile[] = function ($config, Nette\DI\Compiler $compiler) {
856
			$compiler->addExtension('doctrine', new OrmExtension());
857
		};
858
	}
859
860
861
862
	/**
863
	 * @param array $array
864
	 */
865
	private static function natSortKeys(array &$array)
866
	{
867
		$keys = array_keys($array);
868
		natsort($keys);
869
		$keys = array_flip(array_reverse($keys, TRUE));
870
		$array = array_merge($keys, $array);
871
		return $array;
872
	}
873
874
}
875