Completed
Pull Request — master (#266)
by
unknown
10:10
created

OrmExtension::processRepositories()   D

Complexity

Conditions 17
Paths 171

Size

Total Lines 81
Code Lines 52

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
dl 0
loc 81
rs 4.66
cc 17
eloc 52
nc 171
nop 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * This file is part of the Kdyby (http://www.kdyby.org)
5
 *
6
 * Copyright (c) 2008 Filip Procházka ([email protected])
7
 *
8
 * For the full copyright and license information, please view the file license.txt that was distributed with this source code.
9
 */
10
11
namespace Kdyby\Doctrine\DI;
12
13
use Doctrine;
14
use Doctrine\Common\Proxy\AbstractProxyFactory;
15
use 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
			if (count($this->compiler->getExtensions('Kdyby\Events\DI\EventsExtension')) === 0) {
191
				throw new Nette\Utils\AssertionException('The option \'targetEntityMappings\' required \'Kdyby\Events\DI\EventsExtension\'.', E_USER_NOTICE);
192
			}
193
194
			$listener = $builder->addDefinition($this->prefix('resolveTargetEntityListener'))
195
				->setClass('Kdyby\Doctrine\Tools\ResolveTargetEntityListener')
196
				->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...
197
				->setInject(FALSE);
198
199
			foreach ($this->targetEntityMappings as $originalEntity => $mapping) {
200
				$listener->addSetup('addResolveTargetEntity', [$originalEntity, $mapping['targetEntity'], $mapping]);
201
			}
202
		}
203
204
		$this->loadConsole();
205
206
		$builder->addDefinition($this->prefix('registry'))
207
			->setClass('Kdyby\Doctrine\Registry', [
208
				$this->configuredConnections,
209
				$this->configuredManagers,
210
				$builder->parameters[$this->name]['dbal']['defaultConnection'],
211
				$builder->parameters[$this->name]['orm']['defaultEntityManager'],
212
			]);
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
		$entityManagerArguments = [
358
			$connectionService = $this->processConnection($name, $defaults, $isDefault),
359
			$this->prefix('@' . $name . '.ormConfiguration'),
360
		];
361
362
		if (class_exists('Kdyby\Events\NamespacedEventManager')) {
363
			$builder->addDefinition($this->prefix($name . '.evm'))
364
				->setClass('Kdyby\Events\NamespacedEventManager', [Kdyby\Doctrine\Events::NS . '::'])
365
				->addSetup('$dispatchGlobalEvents', [TRUE]) // for BC
366
				->setAutowired(FALSE);
367
368
			$entityManagerArguments[] = $this->prefix('@' . $name . '.evm');
369
		}
370
371
		// entity manager
372
		$entityManager = $builder->addDefinition($managerServiceId = $this->prefix($name . '.entityManager'))
373
			->setClass('Kdyby\Doctrine\EntityManager')
374
			->setFactory('Kdyby\Doctrine\EntityManager::create', $entityManagerArguments)
375
			->addTag(self::TAG_ENTITY_MANAGER)
376
			->addTag('kdyby.doctrine.entityManager')
377
			->setAutowired($isDefault)
378
			->setInject(FALSE);
379
380
		if ($this->isTracyPresent()) {
381
			$entityManager->addSetup('?->bindEntityManager(?)', [$this->prefix('@' . $name . '.diagnosticsPanel'), '@self']);
382
		}
383
384
		$builder->addDefinition($this->prefix('repositoryFactory.' . $name . '.defaultRepositoryFactory'))
385
				->setClass($config['defaultRepositoryClassName'])
386
				->setImplement('Kdyby\Doctrine\DI\IRepositoryFactory')
387
				->setArguments([new Code\PhpLiteral('$entityManager'), new Code\PhpLiteral('$classMetadata')])
388
				->setParameters(['Doctrine\ORM\EntityManagerInterface entityManager', 'Doctrine\ORM\Mapping\ClassMetadata classMetadata'])
389
				->setAutowired(FALSE);
390
391
		$builder->addDefinition($this->prefix($name . '.schemaValidator'))
392
			->setClass('Doctrine\ORM\Tools\SchemaValidator', ['@' . $managerServiceId])
393
			->setAutowired($isDefault);
394
395
		$builder->addDefinition($this->prefix($name . '.schemaTool'))
396
			->setClass('Doctrine\ORM\Tools\SchemaTool', ['@' . $managerServiceId])
397
			->setAutowired($isDefault);
398
399
		$cacheCleaner = $builder->addDefinition($this->prefix($name . '.cacheCleaner'))
400
			->setClass('Kdyby\Doctrine\Tools\CacheCleaner', ['@' . $managerServiceId])
401
			->setAutowired($isDefault);
402
403
		$builder->addDefinition($this->prefix($name . '.schemaManager'))
404
			->setClass('Doctrine\DBAL\Schema\AbstractSchemaManager')
405
			->setFactory('@Kdyby\Doctrine\Connection::getSchemaManager')
406
			->setAutowired($isDefault);
407
408
		foreach ($this->compiler->getExtensions('Kdyby\Annotations\DI\AnnotationsExtension') as $extension) {
409
			/** @var Kdyby\Annotations\DI\AnnotationsExtension $extension */
410
			$cacheCleaner->addSetup('addCacheStorage', [$extension->prefix('@cache.annotations')]);
411
		}
412
413
		if ($isDefault) {
414
			$builder->addDefinition($this->prefix('helper.entityManager'))
415
				->setClass('Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper', ['@' . $managerServiceId])
416
				->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...
417
418
			$builder->addDefinition($this->prefix('helper.connection'))
419
				->setClass('Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper', [$connectionService])
420
				->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...
421
422
			$builder->addAlias($this->prefix('schemaValidator'), $this->prefix($name . '.schemaValidator'));
423
			$builder->addAlias($this->prefix('schemaTool'), $this->prefix($name . '.schemaTool'));
424
			$builder->addAlias($this->prefix('cacheCleaner'), $this->prefix($name . '.cacheCleaner'));
425
			$builder->addAlias($this->prefix('schemaManager'), $this->prefix($name . '.schemaManager'));
426
		}
427
428
		$this->configuredManagers[$name] = $managerServiceId;
429
		$this->managerConfigs[$name] = $config;
430
	}
431
432
433
434
	protected function processSecondLevelCache($name, array $config, $isDefault)
435
	{
436
		if (!$config['enabled']) {
437
			return;
438
		}
439
440
		$builder = $this->getContainerBuilder();
441
442
		$cacheFactory = $builder->addDefinition($this->prefix($name . '.cacheFactory'))
443
			->setClass('Doctrine\ORM\Cache\CacheFactory')
444
			->setFactory($config['factoryClass'], [
445
				$this->prefix('@' . $name . '.cacheRegionsConfiguration'),
446
				$this->processCache($config['driver'], $name . '.secondLevel'),
447
			])
448
			->setAutowired($isDefault);
449
450
		if ($config['factoryClass'] === $this->managerDefaults['secondLevelCache']['factoryClass']
451
			|| is_subclass_of($config['factoryClass'], $this->managerDefaults['secondLevelCache']['factoryClass'])
452
		) {
453
			$cacheFactory->addSetup('setFileLockRegionDirectory', [$config['fileLockRegionDirectory']]);
454
		}
455
456
		$builder->addDefinition($this->prefix($name . '.cacheRegionsConfiguration'))
457
			->setClass('Doctrine\ORM\Cache\RegionsConfiguration', [
458
				$config['regions']['defaultLifetime'],
459
				$config['regions']['defaultLockLifetime'],
460
			])
461
			->setAutowired($isDefault);
462
463
		$logger = $builder->addDefinition($this->prefix($name . '.cacheLogger'))
464
			->setClass('Doctrine\ORM\Cache\Logging\CacheLogger')
465
			->setFactory('Doctrine\ORM\Cache\Logging\CacheLoggerChain')
466
			->setAutowired(FALSE);
467
468
		if ($config['logging']) {
469
			$logger->addSetup('setLogger', [
470
				'statistics',
471
				new Statement('Doctrine\ORM\Cache\Logging\StatisticsCacheLogger')
472
			]);
473
		}
474
475
		$builder->addDefinition($cacheConfigName = $this->prefix($name . '.ormCacheConfiguration'))
476
			->setClass('Doctrine\ORM\Cache\CacheConfiguration')
477
			->addSetup('setCacheFactory', [$this->prefix('@' . $name . '.cacheFactory')])
478
			->addSetup('setCacheLogger', [$this->prefix('@' . $name . '.cacheLogger')])
479
			->setAutowired($isDefault);
480
481
		$configuration = $builder->getDefinition($this->prefix($name . '.ormConfiguration'));
482
		$configuration->addSetup('setSecondLevelCacheEnabled');
483
		$configuration->addSetup('setSecondLevelCacheConfiguration', ['@' . $cacheConfigName]);
484
	}
485
486
487
488
	protected function processConnection($name, array $defaults, $isDefault = FALSE)
489
	{
490
		$builder = $this->getContainerBuilder();
491
		$config = $this->resolveConfig($defaults, $this->connectionDefaults, $this->managerDefaults);
492
493
		if ($isDefault) {
494
			$builder->parameters[$this->name]['dbal']['defaultConnection'] = $name;
495
		}
496
497
		if (isset($defaults['connection'])) {
498
			return $this->prefix('@' . $defaults['connection'] . '.connection');
499
		}
500
501
		// config
502
		$configuration = $builder->addDefinition($this->prefix($name . '.dbalConfiguration'))
503
			->setClass('Doctrine\DBAL\Configuration')
504
			->addSetup('setResultCacheImpl', [$this->processCache($config['resultCache'], $name . '.dbalResult')])
505
			->addSetup('setSQLLogger', [new Statement('Doctrine\DBAL\Logging\LoggerChain')])
506
			->addSetup('setFilterSchemaAssetsExpression', [$config['schemaFilter']])
507
			->setAutowired(FALSE)
508
			->setInject(FALSE);
509
510
		// types
511
		Validators::assertField($config, 'types', 'array');
512
		$schemaTypes = $dbalTypes = [];
513
		foreach ($config['types'] as $dbType => $className) {
514
			$typeInst = Code\Helpers::createObject($className, []);
515
			/** @var Doctrine\DBAL\Types\Type $typeInst */
516
			$dbalTypes[$typeInst->getName()] = $className;
517
			$schemaTypes[$dbType] = $typeInst->getName();
518
		}
519
520
		// tracy panel
521
		if ($this->isTracyPresent()) {
522
			$builder->addDefinition($this->prefix($name . '.diagnosticsPanel'))
523
				->setClass('Kdyby\Doctrine\Diagnostics\Panel')
524
				->setAutowired(FALSE);
525
		}
526
527
		// connection
528
		$options = array_diff_key($config, array_flip(['types', 'resultCache', 'connection', 'logging']));
529
530
		$connectionArguments = [
531
			$options,
532
			$this->prefix('@' . $name . '.dbalConfiguration'),
533
		];
534
535
		if (class_exists('Kdyby\Events\NamespacedEventManager')) {
536
			$connectionArguments[] = $this->prefix('@' . $name . '.evm');
537
		}
538
539
		$connection = $builder->addDefinition($connectionServiceId = $this->prefix($name . '.connection'))
540
			->setClass('Kdyby\Doctrine\Connection')
541
			->setFactory('Kdyby\Doctrine\Connection::create', $connectionArguments)
542
			->addSetup('setSchemaTypes', [$schemaTypes])
543
			->addSetup('setDbalTypes', [$dbalTypes])
544
			->addTag(self::TAG_CONNECTION)
545
			->addTag('kdyby.doctrine.connection')
546
			->setAutowired($isDefault)
547
			->setInject(FALSE);
548
549
		if ($this->isTracyPresent()) {
550
			$connection->addSetup('$panel = ?->bindConnection(?)', [$this->prefix('@' . $name . '.diagnosticsPanel'), '@self']);
551
		}
552
553
		/** @var Nette\DI\ServiceDefinition $connection */
554
555
		$this->configuredConnections[$name] = $connectionServiceId;
556
557
		if (!is_bool($config['logging'])) {
558
			$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...
559
			$configuration->addSetup('$service->getSQLLogger()->addLogger(?)', [$fileLogger]);
560
561
		} elseif ($config['logging']) {
562
			$connection->addSetup('?->enableLogging()', [new Code\PhpLiteral('$panel')]);
563
		}
564
565
		return $this->prefix('@' . $name . '.connection');
566
	}
567
568
569
570
	/**
571
	 * @param \Nette\DI\ServiceDefinition $metadataDriver
572
	 * @param string $namespace
573
	 * @param string|object $driver
574
	 * @param string $prefix
575
	 * @throws \Nette\Utils\AssertionException
576
	 * @return string
577
	 */
578
	protected function processMetadataDriver(Nette\DI\ServiceDefinition $metadataDriver, $namespace, $driver, $prefix)
579
	{
580
		if (!is_string($namespace) || !Strings::match($namespace, '#^' . self::PHP_NAMESPACE . '\z#')) {
581
			throw new Nette\Utils\AssertionException("The metadata namespace expects to be valid namespace, $namespace given.");
582
		}
583
		$namespace = ltrim($namespace, '\\');
584
585
		if (is_string($driver) || is_array($driver)) {
586
			$paths = is_array($driver) ? $driver : [$driver];
587
			foreach ($paths as $path) {
588
				if (($pos = strrpos($path, '*')) !== FALSE) {
589
					$path = substr($path, 0, $pos);
590
				}
591
592
				if (!file_exists($path)) {
593
					throw new Nette\Utils\AssertionException("The metadata path expects to be an existing directory, $path given.");
594
				}
595
			}
596
597
			$driver = new Statement(self::ANNOTATION_DRIVER, is_array($paths) ? $paths : [$paths]);
598
		}
599
600
		$impl = $driver instanceof \stdClass ? $driver->value : ($driver instanceof Statement ? $driver->entity : (string) $driver);
601
		list($driver) = CacheHelpers::filterArgs($driver);
602
		/** @var Statement $driver */
603
604
		if (isset($this->metadataDriverClasses[$impl])) {
605
			$driver->entity = $this->metadataDriverClasses[$impl];
606
		}
607
608
		if (is_string($driver->entity) && substr($driver->entity, 0, 1) === '@') {
609
			$metadataDriver->addSetup('addDriver', [$driver->entity, $namespace]);
610
			return $driver->entity;
611
		}
612
613
		if ($impl === self::ANNOTATION_DRIVER) {
614
			$driver->arguments = [
615
				Nette\Utils\Arrays::flatten($driver->arguments),
616
				2 => $this->prefix('@cache.' . $prefix . '.metadata')
617
			];
618
		}
619
620
		$serviceName = $this->prefix($prefix . '.driver.' . str_replace('\\', '_', $namespace) . '.' . str_replace('\\', '_', $impl) . 'Impl');
621
622
		$this->getContainerBuilder()->addDefinition($serviceName)
623
			->setClass('Doctrine\Common\Persistence\Mapping\Driver\MappingDriver')
624
			->setFactory($driver->entity, $driver->arguments)
625
			->setAutowired(FALSE)
626
			->setInject(FALSE);
627
628
		$metadataDriver->addSetup('addDriver', ['@' . $serviceName, $namespace]);
629
		return '@' . $serviceName;
630
	}
631
632
633
634
	/**
635
	 * @param string|\stdClass $cache
636
	 * @param string $suffix
637
	 * @return string
638
	 */
639
	protected function processCache($cache, $suffix)
640
	{
641
		return CacheHelpers::processCache($this, $cache, $suffix, $this->getContainerBuilder()->parameters[$this->prefix('debug')]);
642
	}
643
644
645
646
	public function beforeCompile()
647
	{
648
		$builder = $this->getContainerBuilder();
649
650
		$disabled = TRUE;
651
		foreach ($this->configuredManagers as $managerName => $_) {
652
			$factoryClassName = $builder->getDefinition($this->prefix($managerName . '.repositoryFactory'))->class;
653
			if ($factoryClassName === 'Kdyby\Doctrine\RepositoryFactory' || in_array('Kdyby\Doctrine\RepositoryFactory', class_parents($factoryClassName), TRUE)) {
654
				$disabled = FALSE;
655
			}
656
		}
657
658
		if ($disabled) {
659
			return;
660
		}
661
662
		if (!method_exists($builder, 'findByType')) {
663
			foreach ($this->configuredManagers as $managerName => $_) {
664
				$builder->getDefinition($this->prefix($managerName . '.repositoryFactory'))
665
					->addSetup('setServiceIdsMap', [[], $this->prefix('repositoryFactory.' . $managerName . '.defaultRepositoryFactory')]);
666
			}
667
668
			return;
669
		}
670
671
		$serviceMap = array_fill_keys(array_keys($this->configuredManagers), []);
672
		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...
673
			if (is_string($originalDef)) {
674
				$originalServiceName = $originalDef;
675
				$originalDef = $builder->getDefinition($originalServiceName);
676
			}
677
678
			if (strpos($originalServiceName, $this->name . '.') === 0) {
679
				continue; // ignore
680
			}
681
682
			$factory = $originalDef->getFactory() ? $originalDef->getFactory()->getEntity() : $originalDef->getClass();
683
			if (stripos($factory, '::getRepository') !== FALSE) {
684
				continue; // ignore
685
			}
686
687
			$factoryServiceName = $this->prefix('repositoryFactory.' . $originalServiceName);
688
			$factoryDef = $builder->addDefinition($factoryServiceName, $originalDef)
689
				->setImplement('Kdyby\Doctrine\DI\IRepositoryFactory')
690
				->setParameters(['Doctrine\ORM\EntityManagerInterface entityManager', 'Doctrine\ORM\Mapping\ClassMetadata classMetadata'])
691
				->setAutowired(FALSE);
692
			$factoryStatement = $factoryDef->getFactory() ?: new Statement($factoryDef->getClass());
693
			$factoryStatement->arguments[0] = new Code\PhpLiteral('$entityManager');
694
			$factoryStatement->arguments[1] = new Code\PhpLiteral('$classMetadata');
695
			$factoryDef->setArguments($factoryStatement->arguments);
696
697
			$boundManagers = $this->getServiceBoundManagers($originalDef);
698
			Validators::assert($boundManagers, 'list:1', 'bound manager');
699
700
			if ($boundEntity = $originalDef->getTag(self::TAG_REPOSITORY_ENTITY)) {
701
				if (!is_string($boundEntity) || !class_exists($boundEntity)) {
702
					throw new Nette\Utils\AssertionException(sprintf('The entity "%s" for repository "%s" cannot be autoloaded.', $boundEntity, $originalDef->class));
703
				}
704
				$entityArgument = $boundEntity;
705
706
			} else {
707
				$entityArgument = new Code\PhpLiteral('"%entityName%"');
708
				$this->postCompileRepositoriesQueue[$boundManagers[0]][] = [ltrim($originalDef->class, '\\'), $originalServiceName];
709
			}
710
711
			$builder->removeDefinition($originalServiceName);
712
			$builder->addDefinition($originalServiceName)
713
				->setClass($originalDef->class)
714
				->setFactory(sprintf('@%s::getRepository', $this->configuredManagers[$boundManagers[0]]), [$entityArgument]);
715
716
			$serviceMap[$boundManagers[0]][$originalDef->class] = $factoryServiceName;
717
		}
718
719
		foreach ($this->configuredManagers as $managerName => $_) {
720
			$builder->getDefinition($this->prefix($managerName . '.repositoryFactory'))
721
				->addSetup('setServiceIdsMap', [
722
					$serviceMap[$managerName],
723
					$this->prefix('repositoryFactory.' . $managerName . '.defaultRepositoryFactory')
724
				]);
725
		}
726
	}
727
728
729
730
	/**
731
	 * @param Nette\DI\ServiceDefinition $def
732
	 * @return string[]
733
	 */
734
	protected function getServiceBoundManagers(Nette\DI\ServiceDefinition $def)
735
	{
736
		$builder = $this->getContainerBuilder();
737
		$boundManagers = $def->getTag(self::TAG_BIND_TO_MANAGER);
738
739
		return is_array($boundManagers) ? $boundManagers : [$builder->parameters[$this->name]['orm']['defaultEntityManager']];
740
	}
741
742
743
744
	public function afterCompile(Code\ClassType $class)
745
	{
746
		$init = $class->methods['initialize'];
747
748
		if ($this->isTracyPresent()) {
749
			$init->addBody('Kdyby\Doctrine\Diagnostics\Panel::registerBluescreen($this);');
750
			$this->addCollapsePathsToTracy($init);
751
		}
752
753
		foreach ($this->proxyAutoloaders as $namespace => $dir) {
754
			$originalInitialize = $init->getBody();
755
			$init->setBody('Kdyby\Doctrine\Proxy\ProxyAutoloader::create(?, ?)->register();', [$dir, $namespace]);
756
			$init->addBody($originalInitialize);
757
		}
758
759
		$this->processRepositoryFactoryEntities($class);
760
	}
761
762
763
764
	protected function processRepositoryFactoryEntities(Code\ClassType $class)
765
	{
766
		if (empty($this->postCompileRepositoriesQueue)) {
767
			return;
768
		}
769
770
		$dic = self::evalAndInstantiateContainer($class);
771
772
		foreach ($this->postCompileRepositoriesQueue as $manager => $items) {
773
			$config = $this->managerConfigs[$manager];
774
			/** @var Kdyby\Doctrine\EntityManager $entityManager */
775
			$entityManager = $dic->getService($this->configuredManagers[$manager]);
776
			/** @var Doctrine\ORM\Mapping\ClassMetadata $entityMetadata */
777
			$metadataFactory = $entityManager->getMetadataFactory();
778
779
			$allMetadata = [];
780
			foreach ($metadataFactory->getAllMetadata() as $entityMetadata) {
781
				if ($config['defaultRepositoryClassName'] === $entityMetadata->customRepositoryClassName || empty($entityMetadata->customRepositoryClassName)) {
782
					continue;
783
				}
784
785
				$allMetadata[ltrim($entityMetadata->customRepositoryClassName, '\\')] = $entityMetadata;
786
			}
787
788
			foreach ($items as $item) {
789
				if (!isset($allMetadata[$item[0]])) {
790
					throw new Nette\Utils\AssertionException(sprintf('Repository class %s have been found in DIC, but no entity has it assigned and it has no entity configured', $item[0]));
791
				}
792
793
				$entityMetadata = $allMetadata[$item[0]];
794
				$serviceMethod = Nette\DI\Container::getMethodName($item[1]);
795
796
				$method = $class->getMethod($serviceMethod);
797
				$methodBody = $method->getBody();
798
				$method->setBody(str_replace('"%entityName%"', Code\Helpers::format('?', $entityMetadata->getName()), $methodBody));
799
			}
800
		}
801
	}
802
803
804
805
	/**
806
	 * @param Code\ClassType $class
807
	 * @return Nette\DI\Container
808
	 */
809
	private static function evalAndInstantiateContainer(Code\ClassType $class)
810
	{
811
		$classCopy = clone $class;
812
		$classCopy->setName($className = 'Kdyby_Doctrine_IamTheKingOfHackingNette_' . $class->getName() . '_' . rand());
0 ignored issues
show
Deprecated Code introduced by
The method Nette\PhpGenerator\ClassType::setName() has been deprecated.

This method has been deprecated.

Loading history...
813
814
		$containerCode = "$classCopy";
815
816
		return call_user_func(function () use ($className, $containerCode) {
817
			eval($containerCode);
0 ignored issues
show
Coding Style introduced by
It is generally not recommended to use eval unless absolutely required.

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

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