Completed
Push — master ( d21dfc...53c47c )
by Filip
02:58
created

OrmExtension::loadConfiguration()   C

Complexity

Conditions 12
Paths 13

Size

Total Lines 66
Code Lines 43

Duplication

Lines 0
Ratio 0 %

Importance

Changes 11
Bugs 4 Features 2
Metric Value
c 11
b 4
f 2
dl 0
loc 66
rs 5.9123
cc 12
eloc 43
nc 13
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
			'enum' => 'Kdyby\Doctrine\Types\Enum',
98
		],
99
		'schemaFilter' => NULL,
100
	];
101
102
	/**
103
	 * @var array
104
	 */
105
	public $metadataDriverClasses = [
106
		self::ANNOTATION_DRIVER => 'Kdyby\Doctrine\Mapping\AnnotationDriver',
107
		'static' => 'Doctrine\Common\Persistence\Mapping\Driver\StaticPHPDriver',
108
		'yml' => 'Doctrine\ORM\Mapping\Driver\YamlDriver',
109
		'yaml' => 'Doctrine\ORM\Mapping\Driver\YamlDriver',
110
		'xml' => 'Doctrine\ORM\Mapping\Driver\XmlDriver',
111
		'db' => 'Doctrine\ORM\Mapping\Driver\DatabaseDriver',
112
	];
113
114
	/**
115
	 * @var array
116
	 */
117
	private $proxyAutoloaders = [];
118
119
	/**
120
	 * @var array
121
	 */
122
	private $targetEntityMappings = [];
123
124
	/**
125
	 * @var array
126
	 */
127
	private $configuredManagers = [];
128
129
	/**
130
	 * @var array
131
	 */
132
	private $managerConfigs = [];
133
134
	/**
135
	 * @var array
136
	 */
137
	private $configuredConnections = [];
138
139
	/**
140
	 * @var array
141
	 */
142
	private $postCompileRepositoriesQueue = [];
143
144
145
146
	public function loadConfiguration()
147
	{
148
		$this->proxyAutoloaders =
149
		$this->targetEntityMappings =
150
		$this->configuredConnections =
151
		$this->managerConfigs =
152
		$this->configuredManagers =
153
		$this->postCompileRepositoriesQueue = [];
154
155
		$extensions = array_filter($this->compiler->getExtensions(), function ($item) {
156
			return $item instanceof Kdyby\Annotations\DI\AnnotationsExtension;
157
		});
158
		if (empty($extensions)) {
159
			throw new Nette\Utils\AssertionException('You should register \'Kdyby\Annotations\DI\AnnotationsExtension\' before \'' . get_class($this) . '\'.', E_USER_NOTICE);
160
		}
161
162
		$builder = $this->getContainerBuilder();
163
		$config = $this->getConfig();
164
165
		$builder->parameters[$this->prefix('debug')] = !empty($config['debug']);
166
		if (isset($config['dbname']) || isset($config['driver']) || isset($config['connection'])) {
167
			$config = ['default' => $config];
168
			$defaults = ['debug' => $builder->parameters['debugMode']];
169
170
		} else {
171
			$defaults = array_intersect_key($config, $this->managerDefaults)
172
				+ array_intersect_key($config, $this->connectionDefaults)
173
				+ ['debug' => $builder->parameters['debugMode']];
174
175
			$config = array_diff_key($config, $defaults);
176
		}
177
178
		if (empty($config)) {
179
			throw new Kdyby\Doctrine\UnexpectedValueException("Please configure the Doctrine extensions using the section '{$this->name}:' in your config file.");
180
		}
181
182
		foreach ($config as $name => $emConfig) {
183
			if (!is_array($emConfig) || (empty($emConfig['dbname']) && empty($emConfig['driver']))) {
184
				throw new Kdyby\Doctrine\UnexpectedValueException("Please configure the Doctrine extensions using the section '{$this->name}:' in your config file.");
185
			}
186
187
			$emConfig = Nette\DI\Config\Helpers::merge($emConfig, $defaults);
188
			$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 187 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...
189
		}
190
191
		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...
192
			$listener = $builder->addDefinition($this->prefix('resolveTargetEntityListener'))
193
				->setClass('Kdyby\Doctrine\Tools\ResolveTargetEntityListener')
194
				->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...
195
				->setInject(FALSE);
196
197
			foreach ($this->targetEntityMappings as $originalEntity => $mapping) {
198
				$listener->addSetup('addResolveTargetEntity', [$originalEntity, $mapping['targetEntity'], $mapping]);
199
			}
200
		}
201
202
		$this->loadConsole();
203
204
		$builder->addDefinition($this->prefix('registry'))
205
			->setClass('Kdyby\Doctrine\Registry', [
206
				$this->configuredConnections,
207
				$this->configuredManagers,
208
				$builder->parameters[$this->name]['dbal']['defaultConnection'],
209
				$builder->parameters[$this->name]['orm']['defaultEntityManager'],
210
			]);
211
	}
212
213
214
215
	protected function loadConsole()
216
	{
217
		$builder = $this->getContainerBuilder();
218
219
		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...
220
			$cli = $builder->addDefinition($this->prefix('cli.' . $i))
221
				->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...
222
				->setInject(FALSE); // lazy injects
223
224
			if (is_string($command)) {
225
				$cli->setClass($command);
226
227
			} else {
228
				throw new Kdyby\Doctrine\NotSupportedException;
229
			}
230
		}
231
	}
232
233
234
235
	protected function processEntityManager($name, array $defaults)
236
	{
237
		$builder = $this->getContainerBuilder();
238
		$config = $this->resolveConfig($defaults, $this->managerDefaults, $this->connectionDefaults);
239
240
		if ($isDefault = !isset($builder->parameters[$this->name]['orm']['defaultEntityManager'])) {
241
			$builder->parameters[$this->name]['orm']['defaultEntityManager'] = $name;
242
		}
243
244
		$metadataDriver = $builder->addDefinition($this->prefix($name . '.metadataDriver'))
245
			->setClass('Doctrine\Common\Persistence\Mapping\Driver\MappingDriverChain')
246
			->setAutowired(FALSE)
247
			->setInject(FALSE);
248
		/** @var Nette\DI\ServiceDefinition $metadataDriver */
249
250
		Validators::assertField($config, 'metadata', 'array');
251
		Validators::assertField($config, 'targetEntityMappings', 'array');
252
		$config['targetEntityMappings'] = $this->normalizeTargetEntityMappings($config['targetEntityMappings']);
253
		foreach ($this->compiler->getExtensions() as $extension) {
254
			if ($extension instanceof IEntityProvider) {
255
				$metadata = $extension->getEntityMappings();
256
				Validators::assert($metadata, 'array');
257
				$config['metadata'] = array_merge($config['metadata'], $metadata);
258
			}
259
260
			if ($extension instanceof ITargetEntityProvider) {
261
				$targetEntities = $extension->getTargetEntityMappings();
262
				Validators::assert($targetEntities, 'array');
263
				$config['targetEntityMappings'] = Nette\Utils\Arrays::mergeTree($config['targetEntityMappings'], $this->normalizeTargetEntityMappings($targetEntities));
264
			}
265
266
			if ($extension instanceof IDatabaseTypeProvider) {
267
				$providedTypes = $extension->getDatabaseTypes();
268
				Validators::assert($providedTypes, 'array');
269
270
				if (!isset($defaults['types'])) {
271
					$defaults['types'] = [];
272
				}
273
274
				$defaults['types'] = array_merge($defaults['types'], $providedTypes);
275
			}
276
		}
277
278
		foreach (self::natSortKeys($config['metadata']) as $namespace => $driver) {
279
			$this->processMetadataDriver($metadataDriver, $namespace, $driver, $name);
280
		}
281
282
		$this->processMetadataDriver($metadataDriver, 'Kdyby\\Doctrine', __DIR__ . '/../Entities', $name);
283
284
		if (empty($config['metadata'])) {
285
			$metadataDriver->addSetup('setDefaultDriver', [
286
				new Statement($this->metadataDriverClasses[self::ANNOTATION_DRIVER], [
287
					[$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...
288
					2 => $this->prefix('@cache.' . $name . '.metadata')
289
				])
290
			]);
291
		}
292
293
		if ($config['repositoryFactoryClassName'] === 'default') {
294
			$config['repositoryFactoryClassName'] = 'Doctrine\ORM\Repository\DefaultRepositoryFactory';
295
		}
296
		$builder->addDefinition($this->prefix($name . '.repositoryFactory'))
297
			->setClass($config['repositoryFactoryClassName'])
298
			->setAutowired(FALSE);
299
300
		Validators::assertField($config, 'namespaceAlias', 'array');
301
		Validators::assertField($config, 'hydrators', 'array');
302
		Validators::assertField($config, 'dql', 'array');
303
		Validators::assertField($config['dql'], 'string', 'array');
304
		Validators::assertField($config['dql'], 'numeric', 'array');
305
		Validators::assertField($config['dql'], 'datetime', 'array');
306
		Validators::assertField($config['dql'], 'hints', 'array');
307
308
		$autoGenerateProxyClasses = is_bool($config['autoGenerateProxyClasses'])
309
			? ($config['autoGenerateProxyClasses'] ? AbstractProxyFactory::AUTOGENERATE_ALWAYS : AbstractProxyFactory::AUTOGENERATE_NEVER)
310
			: $config['autoGenerateProxyClasses'];
311
312
		$configuration = $builder->addDefinition($this->prefix($name . '.ormConfiguration'))
313
			->setClass('Kdyby\Doctrine\Configuration')
314
			->addSetup('setMetadataCacheImpl', [$this->processCache($config['metadataCache'], $name . '.metadata')])
315
			->addSetup('setQueryCacheImpl', [$this->processCache($config['queryCache'], $name . '.query')])
316
			->addSetup('setResultCacheImpl', [$this->processCache($config['resultCache'], $name . '.ormResult')])
317
			->addSetup('setHydrationCacheImpl', [$this->processCache($config['hydrationCache'], $name . '.hydration')])
318
			->addSetup('setMetadataDriverImpl', [$this->prefix('@' . $name . '.metadataDriver')])
319
			->addSetup('setClassMetadataFactoryName', [$config['classMetadataFactory']])
320
			->addSetup('setDefaultRepositoryClassName', [$config['defaultRepositoryClassName']])
321
			->addSetup('setQueryBuilderClassName', [$config['queryBuilderClassName']])
322
			->addSetup('setRepositoryFactory', [$this->prefix('@' . $name . '.repositoryFactory')])
323
			->addSetup('setProxyDir', [$config['proxyDir']])
324
			->addSetup('setProxyNamespace', [$config['proxyNamespace']])
325
			->addSetup('setAutoGenerateProxyClasses', [$autoGenerateProxyClasses])
326
			->addSetup('setEntityNamespaces', [$config['namespaceAlias']])
327
			->addSetup('setCustomHydrationModes', [$config['hydrators']])
328
			->addSetup('setCustomStringFunctions', [$config['dql']['string']])
329
			->addSetup('setCustomNumericFunctions', [$config['dql']['numeric']])
330
			->addSetup('setCustomDatetimeFunctions', [$config['dql']['datetime']])
331
			->addSetup('setDefaultQueryHints', [$config['dql']['hints']])
332
			->addSetup('setNamingStrategy', CacheHelpers::filterArgs($config['namingStrategy']))
333
			->addSetup('setQuoteStrategy', CacheHelpers::filterArgs($config['quoteStrategy']))
334
			->addSetup('setEntityListenerResolver', CacheHelpers::filterArgs($config['entityListenerResolver']))
335
			->setAutowired(FALSE)
336
			->setInject(FALSE);
337
		/** @var Nette\DI\ServiceDefinition $configuration */
338
339
		$builder->addDefinition($this->prefix($name . '.proxyAutoloader'))
340
			->setClass('Kdyby\Doctrine\Proxy\ProxyAutoloader')
341
			->setArguments([$config['proxyDir'], $config['proxyNamespace']])
342
			->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...
343
			->setAutowired(FALSE)
344
			->setInject(FALSE);
345
346
		$this->proxyAutoloaders[$config['proxyNamespace']] = $config['proxyDir'];
347
348
		$this->processSecondLevelCache($name, $config['secondLevelCache'], $isDefault);
349
350
		Validators::assertField($config, 'filters', 'array');
351
		foreach ($config['filters'] as $filterName => $filterClass) {
352
			$configuration->addSetup('addFilter', [$filterName, $filterClass]);
353
		}
354
355
		if ($config['targetEntityMappings']) {
356
			$configuration->addSetup('setTargetEntityMap', [array_map(function ($mapping) {
357
				return $mapping['targetEntity'];
358
			}, $config['targetEntityMappings'])]);
359
			$this->targetEntityMappings = Nette\Utils\Arrays::mergeTree($this->targetEntityMappings, $config['targetEntityMappings']);
360
		}
361
362
		$builder->addDefinition($this->prefix($name . '.evm'))
363
			->setClass('Kdyby\Events\NamespacedEventManager', [Kdyby\Doctrine\Events::NS . '::'])
364
			->addSetup('$dispatchGlobalEvents', [TRUE]) // for BC
365
			->setAutowired(FALSE);
366
367
		// entity manager
368
		$entityManager = $builder->addDefinition($managerServiceId = $this->prefix($name . '.entityManager'))
369
			->setClass('Kdyby\Doctrine\EntityManager')
370
			->setFactory('Kdyby\Doctrine\EntityManager::create', [
371
				$connectionService = $this->processConnection($name, $defaults, $isDefault),
372
				$this->prefix('@' . $name . '.ormConfiguration'),
373
				$this->prefix('@' . $name . '.evm'),
374
			])
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
		$connection = $builder->addDefinition($connectionServiceId = $this->prefix($name . '.connection'))
530
			->setClass('Kdyby\Doctrine\Connection')
531
			->setFactory('Kdyby\Doctrine\Connection::create', [
532
				$options,
533
				$this->prefix('@' . $name . '.dbalConfiguration'),
534
				$this->prefix('@' . $name . '.evm')
535
			])
536
			->addSetup('setSchemaTypes', [$schemaTypes])
537
			->addSetup('setDbalTypes', [$dbalTypes])
538
			->addTag(self::TAG_CONNECTION)
539
			->addTag('kdyby.doctrine.connection')
540
			->setAutowired($isDefault)
541
			->setInject(FALSE);
542
543
		if ($this->isTracyPresent()) {
544
			$connection->addSetup('$panel = ?->bindConnection(?)', [$this->prefix('@' . $name . '.diagnosticsPanel'), '@self']);
545
		}
546
547
		/** @var Nette\DI\ServiceDefinition $connection */
548
549
		$this->configuredConnections[$name] = $connectionServiceId;
550
551
		if (!is_bool($config['logging'])) {
552
			$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...
553
			$configuration->addSetup('$service->getSQLLogger()->addLogger(?)', [$fileLogger]);
554
555
		} elseif ($config['logging']) {
556
			$connection->addSetup('?->enableLogging()', [new Code\PhpLiteral('$panel')]);
557
		}
558
559
		return $this->prefix('@' . $name . '.connection');
560
	}
561
562
563
564
	/**
565
	 * @param \Nette\DI\ServiceDefinition $metadataDriver
566
	 * @param string $namespace
567
	 * @param string|object $driver
568
	 * @param string $prefix
569
	 * @throws \Nette\Utils\AssertionException
570
	 * @return string
571
	 */
572
	protected function processMetadataDriver(Nette\DI\ServiceDefinition $metadataDriver, $namespace, $driver, $prefix)
573
	{
574
		if (!is_string($namespace) || !Strings::match($namespace, '#^' . self::PHP_NAMESPACE . '\z#')) {
575
			throw new Nette\Utils\AssertionException("The metadata namespace expects to be valid namespace, $namespace given.");
576
		}
577
		$namespace = ltrim($namespace, '\\');
578
579
		if (is_string($driver) || is_array($driver)) {
580
			$paths = is_array($driver) ? $driver : [$driver];
581
			foreach ($paths as $path) {
582
				if (($pos = strrpos($path, '*')) !== FALSE) {
583
					$path = substr($path, 0, $pos);
584
				}
585
586
				if (!file_exists($path)) {
587
					throw new Nette\Utils\AssertionException("The metadata path expects to be an existing directory, $path given.");
588
				}
589
			}
590
591
			$driver = new Statement(self::ANNOTATION_DRIVER, is_array($paths) ? $paths : [$paths]);
592
		}
593
594
		$impl = $driver instanceof \stdClass ? $driver->value : ($driver instanceof Statement ? $driver->entity : (string) $driver);
595
		list($driver) = CacheHelpers::filterArgs($driver);
596
		/** @var Statement $driver */
597
598
		if (isset($this->metadataDriverClasses[$impl])) {
599
			$driver->entity = $this->metadataDriverClasses[$impl];
600
		}
601
602
		if (is_string($driver->entity) && substr($driver->entity, 0, 1) === '@') {
603
			$metadataDriver->addSetup('addDriver', [$driver->entity, $namespace]);
604
			return $driver->entity;
605
		}
606
607
		if ($impl === self::ANNOTATION_DRIVER) {
608
			$driver->arguments = [
609
				Nette\Utils\Arrays::flatten($driver->arguments),
610
				2 => $this->prefix('@cache.' . $prefix . '.metadata')
611
			];
612
		}
613
614
		$serviceName = $this->prefix($prefix . '.driver.' . str_replace('\\', '_', $namespace) . '.' . str_replace('\\', '_', $impl) . 'Impl');
615
616
		$this->getContainerBuilder()->addDefinition($serviceName)
617
			->setClass('Doctrine\Common\Persistence\Mapping\Driver\MappingDriver')
618
			->setFactory($driver->entity, $driver->arguments)
619
			->setAutowired(FALSE)
620
			->setInject(FALSE);
621
622
		$metadataDriver->addSetup('addDriver', ['@' . $serviceName, $namespace]);
623
		return '@' . $serviceName;
624
	}
625
626
627
628
	/**
629
	 * @param string|\stdClass $cache
630
	 * @param string $suffix
631
	 * @return string
632
	 */
633
	protected function processCache($cache, $suffix)
634
	{
635
		return CacheHelpers::processCache($this, $cache, $suffix, $this->getContainerBuilder()->parameters[$this->prefix('debug')]);
636
	}
637
638
639
640
	public function beforeCompile()
641
	{
642
		$eventsExt = NULL;
643
		foreach ($this->compiler->getExtensions() as $extension) {
644
			if ($extension instanceof Kdyby\Events\DI\EventsExtension) {
645
				$eventsExt = $extension;
646
				break;
647
			}
648
		}
649
650
		if ($eventsExt === NULL) {
651
			throw new Nette\Utils\AssertionException('Please register the required Kdyby\Events\DI\EventsExtension to Compiler.');
652
		}
653
654
		$this->processRepositories();
655
	}
656
657
658
659
	protected function processRepositories()
660
	{
661
		$builder = $this->getContainerBuilder();
662
663
		$disabled = TRUE;
664
		foreach ($this->configuredManagers as $managerName => $_) {
665
			$factoryClassName = $builder->getDefinition($this->prefix($managerName . '.repositoryFactory'))->class;
666
			if ($factoryClassName === 'Kdyby\Doctrine\RepositoryFactory' || in_array('Kdyby\Doctrine\RepositoryFactory', class_parents($factoryClassName), TRUE)) {
667
				$disabled = FALSE;
668
			}
669
		}
670
671
		if ($disabled) {
672
			return;
673
		}
674
675
		if (!method_exists($builder, 'findByType')) {
676
			foreach ($this->configuredManagers as $managerName => $_) {
677
				$builder->getDefinition($this->prefix($managerName . '.repositoryFactory'))
678
					->addSetup('setServiceIdsMap', [[], $this->prefix('repositoryFactory.' . $managerName . '.defaultRepositoryFactory')]);
679
			}
680
681
			return;
682
		}
683
684
		$serviceMap = array_fill_keys(array_keys($this->configuredManagers), []);
685
		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...
686
			if (is_string($originalDef)) {
687
				$originalServiceName = $originalDef;
688
				$originalDef = $builder->getDefinition($originalServiceName);
689
			}
690
691
			if (strpos($originalServiceName, $this->name . '.') === 0) {
692
				continue; // ignore
693
			}
694
695
			$factory = $originalDef->getFactory() ? $originalDef->getFactory()->getEntity() : $originalDef->getClass();
696
			if (stripos($factory, '::getRepository') !== FALSE) {
697
				continue; // ignore
698
			}
699
700
			$factoryServiceName = $this->prefix('repositoryFactory.' . $originalServiceName);
701
			$factoryDef = $builder->addDefinition($factoryServiceName, $originalDef)
702
				->setImplement('Kdyby\Doctrine\DI\IRepositoryFactory')
703
				->setParameters(['Doctrine\ORM\EntityManagerInterface entityManager', 'Doctrine\ORM\Mapping\ClassMetadata classMetadata'])
704
				->setAutowired(FALSE);
705
			$factoryStatement = $factoryDef->getFactory() ?: new Statement($factoryDef->getClass());
706
			$factoryStatement->arguments[0] = new Code\PhpLiteral('$entityManager');
707
			$factoryStatement->arguments[1] = new Code\PhpLiteral('$classMetadata');
708
			$factoryDef->setArguments($factoryStatement->arguments);
709
710
			$boundManagers = $this->getServiceBoundManagers($originalDef);
711
			Validators::assert($boundManagers, 'list:1', 'bound manager');
712
713
			if ($boundEntity = $originalDef->getTag(self::TAG_REPOSITORY_ENTITY)) {
714
				if (!is_string($boundEntity) || !class_exists($boundEntity)) {
715
					throw new Nette\Utils\AssertionException(sprintf('The entity "%s" for repository "%s" cannot be autoloaded.', $boundEntity, $originalDef->class));
716
				}
717
				$entityArgument = $boundEntity;
718
719
			} else {
720
				$entityArgument = new Code\PhpLiteral('"%entityName%"');
721
				$this->postCompileRepositoriesQueue[$boundManagers[0]][] = [ltrim($originalDef->class, '\\'), $originalServiceName];
722
			}
723
724
			$builder->removeDefinition($originalServiceName);
725
			$builder->addDefinition($originalServiceName)
726
				->setClass($originalDef->class)
727
				->setFactory(sprintf('@%s::getRepository', $this->configuredManagers[$boundManagers[0]]), [$entityArgument]);
728
729
			$serviceMap[$boundManagers[0]][$originalDef->class] = $factoryServiceName;
730
		}
731
732
		foreach ($this->configuredManagers as $managerName => $_) {
733
			$builder->getDefinition($this->prefix($managerName . '.repositoryFactory'))
734
				->addSetup('setServiceIdsMap', [
735
					$serviceMap[$managerName],
736
					$this->prefix('repositoryFactory.' . $managerName . '.defaultRepositoryFactory')
737
				]);
738
		}
739
	}
740
741
742
743
	/**
744
	 * @param Nette\DI\ServiceDefinition $def
745
	 * @return string[]
746
	 */
747
	protected function getServiceBoundManagers(Nette\DI\ServiceDefinition $def)
748
	{
749
		$builder = $this->getContainerBuilder();
750
		$boundManagers = $def->getTag(self::TAG_BIND_TO_MANAGER);
751
752
		return is_array($boundManagers) ? $boundManagers : [$builder->parameters[$this->name]['orm']['defaultEntityManager']];
753
	}
754
755
756
757
	public function afterCompile(Code\ClassType $class)
758
	{
759
		if ($this->isTracyPresent()) {
760
			$init = $class->methods['initialize'];
761
			$init->addBody('Kdyby\Doctrine\Diagnostics\Panel::registerBluescreen($this);');
762
			$this->addCollapsePathsToTracy($init);
763
		}
764
765
		$this->processRepositoryFactoryEntities($class);
766
	}
767
768
769
770
	protected function processRepositoryFactoryEntities(Code\ClassType $class)
771
	{
772
		if (empty($this->postCompileRepositoriesQueue)) {
773
			return;
774
		}
775
776
		$dic = self::evalAndInstantiateContainer($class);
777
778
		foreach ($this->postCompileRepositoriesQueue as $manager => $items) {
779
			$config = $this->managerConfigs[$manager];
780
			/** @var Kdyby\Doctrine\EntityManager $entityManager */
781
			$entityManager = $dic->getService($this->configuredManagers[$manager]);
782
			/** @var Doctrine\ORM\Mapping\ClassMetadata $entityMetadata */
783
			$metadataFactory = $entityManager->getMetadataFactory();
784
785
			$allMetadata = [];
786
			foreach ($metadataFactory->getAllMetadata() as $entityMetadata) {
787
				if ($config['defaultRepositoryClassName'] === $entityMetadata->customRepositoryClassName || empty($entityMetadata->customRepositoryClassName)) {
788
					continue;
789
				}
790
791
				$allMetadata[ltrim($entityMetadata->customRepositoryClassName, '\\')] = $entityMetadata;
792
			}
793
794
			foreach ($items as $item) {
795
				if (!isset($allMetadata[$item[0]])) {
796
					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]));
797
				}
798
799
				$entityMetadata = $allMetadata[$item[0]];
800
				$serviceMethod = Nette\DI\Container::getMethodName($item[1]);
801
802
				$method = $class->getMethod($serviceMethod);
803
				$methodBody = $method->getBody();
804
				$method->setBody(str_replace('"%entityName%"', Code\Helpers::format('?', $entityMetadata->getName()), $methodBody));
805
			}
806
		}
807
	}
808
809
810
811
	/**
812
	 * @param Code\ClassType $class
813
	 * @return Nette\DI\Container
814
	 */
815
	private static function evalAndInstantiateContainer(Code\ClassType $class)
816
	{
817
		$classCopy = clone $class;
818
		$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...
819
820
		$containerCode = "$classCopy";
821
822
		return call_user_func(function () use ($className, $containerCode) {
823
			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...
824
			return new $className();
825
		});
826
	}
827
828
829
830
	/**
831
	 * @param $provided
832
	 * @param $defaults
833
	 * @param $diff
834
	 * @return array
835
	 */
836
	private function resolveConfig(array $provided, array $defaults, array $diff = [])
837
	{
838
		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...
839
			array_diff_key($provided, array_diff_key($diff, $defaults)),
840
			$defaults
841
		));
842
	}
843
844
845
	/**
846
	 * @param array $targetEntityMappings
847
	 * @return array
848
	 */
849
	private function normalizeTargetEntityMappings(array $targetEntityMappings)
850
	{
851
		$normalized = [];
852
		foreach ($targetEntityMappings as $originalEntity => $targetEntity) {
853
			$originalEntity = ltrim($originalEntity, '\\');
854
			Validators::assert($targetEntity, 'array|string');
855
			if (is_array($targetEntity)) {
856
				Validators::assertField($targetEntity, 'targetEntity', 'string');
857
				$mapping = array_merge($targetEntity, [
858
					'targetEntity' => ltrim($targetEntity['targetEntity'], '\\')
859
				]);
860
861
			} else {
862
				$mapping = [
863
					'targetEntity' => ltrim($targetEntity, '\\'),
864
				];
865
			}
866
			$normalized[$originalEntity] = $mapping;
867
		}
868
		return $normalized;
869
	}
870
871
872
873
	/**
874
	 * @return bool
875
	 */
876
	private function isTracyPresent()
877
	{
878
		return interface_exists('Tracy\IBarPanel');
879
	}
880
881
882
883
	private function addCollapsePathsToTracy(Method $init)
884
	{
885
		$blueScreen = 'Tracy\Debugger::getBlueScreen()';
886
		$commonDirname = dirname(Nette\Reflection\ClassType::from('Doctrine\Common\Version')->getFileName());
887
888
		$init->addBody($blueScreen . '->collapsePaths[] = ?;', [dirname(Nette\Reflection\ClassType::from('Kdyby\Doctrine\Exception')->getFileName())]);
889
		$init->addBody($blueScreen . '->collapsePaths[] = ?;', [dirname(dirname(dirname(dirname($commonDirname))))]); // this should be vendor/doctrine
890
		foreach ($this->proxyAutoloaders as $dir) {
891
			$init->addBody($blueScreen . '->collapsePaths[] = ?;', [$dir]);
892
		}
893
	}
894
895
896
897
	/**
898
	 * @param \Nette\Configurator $configurator
899
	 */
900
	public static function register(Nette\Configurator $configurator)
901
	{
902
		$configurator->onCompile[] = function ($config, Nette\DI\Compiler $compiler) {
903
			$compiler->addExtension('doctrine', new OrmExtension());
904
		};
905
	}
906
907
908
909
	/**
910
	 * @param array $array
911
	 */
912
	private static function natSortKeys(array &$array)
913
	{
914
		$keys = array_keys($array);
915
		natsort($keys);
916
		$keys = array_flip(array_reverse($keys, TRUE));
917
		$array = array_merge($keys, $array);
918
		return $array;
919
	}
920
921
}
922