Completed
Pull Request — master (#274)
by
unknown
02:33
created

OrmExtension::afterCompile()   B

Complexity

Conditions 3
Paths 4

Size

Total Lines 26
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

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