Completed
Push — master ( c1951e...846a6f )
by Filip
07:02
created

OrmExtension   F

Complexity

Total Complexity 105

Size/Duplication

Total Lines 859
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 24

Importance

Changes 0
Metric Value
wmc 105
lcom 1
cbo 24
dl 0
loc 859
rs 1.133
c 0
b 0
f 0

19 Methods

Rating   Name   Duplication   Size   Complexity  
C loadConfiguration() 0 66 13
A loadConsole() 0 17 3
F processEntityManager() 0 198 20
B processSecondLevelCache() 0 51 5
C processConnection() 0 71 8
C processMetadataDriver() 0 58 18
A processCache() 0 4 1
A beforeCompile() 0 5 1
D processRepositories() 0 87 17
A processEventManagers() 0 12 4
A getServiceBoundManagers() 0 7 2
A afterCompile() 0 15 3
A resolveConfig() 0 7 1
A normalizeTargetEntityMappings() 0 21 3
A isTracyPresent() 0 4 1
A isKdybyEventsPresent() 0 4 1
A addCollapsePathsToTracy() 0 11 2
A register() 0 6 1
A natSortKeys() 0 8 1

How to fix   Complexity   

Complex Class

Complex classes like OrmExtension often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use OrmExtension, and based on these observations, apply Extract Interface, too.

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 Doctrine\ORM\EntityManagerInterface;
16
use Doctrine\ORM\Repository\DefaultRepositoryFactory;
17
use Kdyby;
18
use Kdyby\DoctrineCache\DI\Helpers as CacheHelpers;
19
use Nette;
20
use Nette\DI\Statement;
21
use Nette\PhpGenerator as Code;
22
use Nette\PhpGenerator\Method;
23
use Nette\Utils\Strings;
24
use Nette\Utils\Validators;
25
use Doctrine\DBAL\Schema\AbstractSchemaManager;
26
use Kdyby\Annotations\DI\AnnotationsExtension;
27
use Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper;
28
use Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper;
29
30
/**
31
 * @author Filip Procházka <[email protected]>
32
 */
33
class OrmExtension extends Nette\DI\CompilerExtension
34
{
35
36
	const ANNOTATION_DRIVER = 'annotations';
37
	const PHP_NAMESPACE = '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff\\\\]*';
38
	const TAG_CONNECTION = 'doctrine.connection';
39
	const TAG_ENTITY_MANAGER = 'doctrine.entityManager';
40
	const TAG_BIND_TO_MANAGER = 'doctrine.bindToManager';
41
	const TAG_REPOSITORY_ENTITY = 'doctrine.repositoryEntity';
42
	const DEFAULT_PROXY_NAMESPACE = 'Kdyby\GeneratedProxy';
43
	const KDYBY_METADATA_NAMESPACE = 'Kdyby\Doctrine';
44
45
	/**
46
	 * @var array
47
	 */
48
	public $managerDefaults = [
49
		'metadataCache' => 'default',
50
		'queryCache' => 'default',
51
		'resultCache' => 'default',
52
		'hydrationCache' => 'default',
53
		'secondLevelCache' => [
54
			'enabled' => FALSE,
55
			'factoryClass' => Doctrine\ORM\Cache\DefaultCacheFactory::class,
56
			'driver' => 'default',
57
			'regions' => [
58
				'defaultLifetime' => 3600,
59
				'defaultLockLifetime' => 60,
60
			],
61
			'fileLockRegionDirectory' => '%tempDir%/cache/Doctrine.Cache.Locks', // todo fix
62
			'logging' => '%debugMode%',
63
		],
64
		'classMetadataFactory' => Kdyby\Doctrine\Mapping\ClassMetadataFactory::class,
65
		'defaultRepositoryClassName' => Kdyby\Doctrine\EntityRepository::class,
66
		'repositoryFactoryClassName' => Kdyby\Doctrine\RepositoryFactory::class,
67
		'queryBuilderClassName' => Kdyby\Doctrine\QueryBuilder::class,
68
		'autoGenerateProxyClasses' => '%debugMode%',
69
		'namingStrategy' => Doctrine\ORM\Mapping\UnderscoreNamingStrategy::class,
70
		'quoteStrategy' => Doctrine\ORM\Mapping\DefaultQuoteStrategy::class,
71
		'entityListenerResolver' => Kdyby\Doctrine\Mapping\EntityListenerResolver::class,
72
		'proxyDir' => '%tempDir%/proxies',
73
		'proxyNamespace' => self::DEFAULT_PROXY_NAMESPACE,
74
		'dql' => ['string' => [], 'numeric' => [], 'datetime' => [], 'hints' => []],
75
		'hydrators' => [],
76
		'metadata' => [],
77
		'filters' => [],
78
		'namespaceAlias' => [],
79
		'targetEntityMappings' => [],
80
	];
81
82
	/**
83
	 * @var array
84
	 */
85
	public $connectionDefaults = [
86
		'dbname' => NULL,
87
		'host' => '127.0.0.1',
88
		'port' => NULL,
89
		'user' => NULL,
90
		'password' => NULL,
91
		'charset' => 'UTF8',
92
		'driver' => 'pdo_mysql',
93
		'driverClass' => NULL,
94
		'options' => NULL,
95
		'path' => NULL,
96
		'memory' => NULL,
97
		'unix_socket' => NULL,
98
		'logging' => '%debugMode%',
99
		'platformService' => NULL,
100
		'defaultTableOptions' => [],
101
		'resultCache' => 'default',
102
		'types' => [],
103
		'types' => [],
104
		'schemaFilter' => NULL,
105
	];
106
107
	/**
108
	 * @var array
109
	 */
110
	public $metadataDriverClasses = [
111
		self::ANNOTATION_DRIVER => Kdyby\Doctrine\Mapping\AnnotationDriver::class,
112
		'static' => Doctrine\Common\Persistence\Mapping\Driver\StaticPHPDriver::class,
113
		'yml' => Doctrine\ORM\Mapping\Driver\YamlDriver::class,
114
		'yaml' => Doctrine\ORM\Mapping\Driver\YamlDriver::class,
115
		'xml' => Doctrine\ORM\Mapping\Driver\XmlDriver::class,
116
		'db' => Doctrine\ORM\Mapping\Driver\DatabaseDriver::class,
117
	];
118
119
	/**
120
	 * @var array
121
	 */
122
	private $proxyAutoloaders = [];
123
124
	/**
125
	 * @var array
126
	 */
127
	private $targetEntityMappings = [];
128
129
	/**
130
	 * @var array
131
	 */
132
	private $configuredManagers = [];
133
134
	/**
135
	 * @var array
136
	 */
137
	private $managerConfigs = [];
138
139
	/**
140
	 * @var array
141
	 */
142
	private $configuredConnections = [];
143
144
145
146
	public function loadConfiguration()
147
	{
148
		$this->proxyAutoloaders =
149
		$this->targetEntityMappings =
150
		$this->configuredConnections =
151
		$this->managerConfigs =
152
		$this->configuredManagers = [];
153
154
		if (!$this->compiler->getExtensions(AnnotationsExtension::class)) {
155
			throw new Nette\Utils\AssertionException(sprintf("You should register %s before %s.", AnnotationsExtension::class, get_class($this)));
156
		}
157
158
		$builder = $this->getContainerBuilder();
159
		$config = $this->getConfig();
160
161
		$builder->parameters[$this->prefix('debug')] = !empty($config['debug']);
162
		if (isset($config['dbname']) || isset($config['driver']) || isset($config['connection'])) {
163
			$config = ['default' => $config];
164
			$defaults = ['debug' => $builder->parameters['debugMode']];
165
166
		} else {
167
			$defaults = array_intersect_key($config, $this->managerDefaults)
168
				+ array_intersect_key($config, $this->connectionDefaults)
169
				+ ['debug' => $builder->parameters['debugMode']];
170
171
			$config = array_diff_key($config, $defaults);
172
		}
173
174
		if (empty($config)) {
175
			throw new Kdyby\Doctrine\UnexpectedValueException("Please configure the Doctrine extensions using the section '{$this->name}:' in your config file.");
176
		}
177
178
		foreach ($config as $name => $emConfig) {
179
			if (!is_array($emConfig) || (empty($emConfig['dbname']) && empty($emConfig['driver']))) {
180
				throw new Kdyby\Doctrine\UnexpectedValueException("Please configure the Doctrine extensions using the section '{$this->name}:' in your config file.");
181
			}
182
183
			/** @var mixed[] $emConfig */
184
			$emConfig = Nette\DI\Config\Helpers::merge($emConfig, $defaults);
185
			$this->processEntityManager($name, $emConfig);
186
		}
187
188
		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...
189
			if (!$this->isKdybyEventsPresent()) {
190
				throw new Nette\Utils\AssertionException('The option \'targetEntityMappings\' requires \'Kdyby\Events\DI\EventsExtension\'.', E_USER_NOTICE);
191
			}
192
193
			$listener = $builder->addDefinition($this->prefix('resolveTargetEntityListener'))
194
				->setClass(Kdyby\Doctrine\Tools\ResolveTargetEntityListener::class)
195
				->addTag(Kdyby\Events\DI\EventsExtension::TAG_SUBSCRIBER);
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::class, [
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::TAG_COMMAND)
222
				->addTag(Nette\DI\Extensions\InjectExtension::TAG_INJECT, 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::class)
246
			->setAutowired(FALSE);
247
		/** @var \Nette\DI\ServiceDefinition $metadataDriver */
248
249
		Validators::assertField($config, 'metadata', 'array');
250
		Validators::assertField($config, 'targetEntityMappings', 'array');
251
		$config['targetEntityMappings'] = $this->normalizeTargetEntityMappings($config['targetEntityMappings']);
252
		foreach ($this->compiler->getExtensions() as $extension) {
253
			if ($extension instanceof IEntityProvider) {
254
				$metadata = $extension->getEntityMappings();
255
				Validators::assert($metadata, 'array');
256
				foreach ($metadata as $namespace => $nsConfig) {
257
					if (array_key_exists($namespace, $config['metadata'])) {
258
						throw new Nette\Utils\AssertionException(sprintf('The namespace %s is already configured, provider cannot change it', $namespace));
259
					}
260
					$config['metadata'][$namespace] = $nsConfig;
261
				}
262
			}
263
264
			if ($extension instanceof ITargetEntityProvider) {
265
				$targetEntities = $extension->getTargetEntityMappings();
266
				Validators::assert($targetEntities, 'array');
267
				$config['targetEntityMappings'] = Nette\Utils\Arrays::mergeTree($config['targetEntityMappings'], $this->normalizeTargetEntityMappings($targetEntities));
268
			}
269
270
			if ($extension instanceof IDatabaseTypeProvider) {
271
				$providedTypes = $extension->getDatabaseTypes();
272
				Validators::assert($providedTypes, 'array');
273
274
				if (!isset($defaults['types'])) {
275
					$defaults['types'] = [];
276
				}
277
278
				$defaults['types'] = array_merge($defaults['types'], $providedTypes);
279
			}
280
		}
281
282
		foreach (self::natSortKeys($config['metadata']) as $namespace => $driver) {
283
			$this->processMetadataDriver($metadataDriver, $namespace, $driver, $name);
284
		}
285
286
		$this->processMetadataDriver($metadataDriver, self::KDYBY_METADATA_NAMESPACE, __DIR__ . '/../Entities', $name);
287
288
		if (empty($config['metadata'])) {
289
			$metadataDriver->addSetup('setDefaultDriver', [
290
				new Statement($this->metadataDriverClasses[self::ANNOTATION_DRIVER], [
291
					[$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...
292
					2 => $this->prefix('@cache.' . $name . '.metadata')
293
				])
294
			]);
295
		}
296
297
		if ($config['repositoryFactoryClassName'] === 'default') {
298
			$config['repositoryFactoryClassName'] = DefaultRepositoryFactory::class;
299
		}
300
		$builder->addDefinition($this->prefix($name . '.repositoryFactory'))
301
			->setClass($config['repositoryFactoryClassName'])
302
			->setAutowired(FALSE);
303
304
		Validators::assertField($config, 'namespaceAlias', 'array');
305
		Validators::assertField($config, 'hydrators', 'array');
306
		Validators::assertField($config, 'dql', 'array');
307
		Validators::assertField($config['dql'], 'string', 'array');
308
		Validators::assertField($config['dql'], 'numeric', 'array');
309
		Validators::assertField($config['dql'], 'datetime', 'array');
310
		Validators::assertField($config['dql'], 'hints', 'array');
311
312
		$autoGenerateProxyClasses = is_bool($config['autoGenerateProxyClasses'])
313
			? ($config['autoGenerateProxyClasses'] ? AbstractProxyFactory::AUTOGENERATE_ALWAYS : AbstractProxyFactory::AUTOGENERATE_NEVER)
314
			: $config['autoGenerateProxyClasses'];
315
316
		$configuration = $builder->addDefinition($this->prefix($name . '.ormConfiguration'))
317
			->setClass(Kdyby\Doctrine\Configuration::class)
318
			->addSetup('setMetadataCacheImpl', [$this->processCache($config['metadataCache'], $name . '.metadata')])
319
			->addSetup('setQueryCacheImpl', [$this->processCache($config['queryCache'], $name . '.query')])
320
			->addSetup('setResultCacheImpl', [$this->processCache($config['resultCache'], $name . '.ormResult')])
321
			->addSetup('setHydrationCacheImpl', [$this->processCache($config['hydrationCache'], $name . '.hydration')])
322
			->addSetup('setMetadataDriverImpl', [$this->prefix('@' . $name . '.metadataDriver')])
323
			->addSetup('setClassMetadataFactoryName', [$config['classMetadataFactory']])
324
			->addSetup('setDefaultRepositoryClassName', [$config['defaultRepositoryClassName']])
325
			->addSetup('setQueryBuilderClassName', [$config['queryBuilderClassName']])
326
			->addSetup('setRepositoryFactory', [$this->prefix('@' . $name . '.repositoryFactory')])
327
			->addSetup('setProxyDir', [$config['proxyDir']])
328
			->addSetup('setProxyNamespace', [$config['proxyNamespace']])
329
			->addSetup('setAutoGenerateProxyClasses', [$autoGenerateProxyClasses])
330
			->addSetup('setEntityNamespaces', [$config['namespaceAlias']])
331
			->addSetup('setCustomHydrationModes', [$config['hydrators']])
332
			->addSetup('setCustomStringFunctions', [$config['dql']['string']])
333
			->addSetup('setCustomNumericFunctions', [$config['dql']['numeric']])
334
			->addSetup('setCustomDatetimeFunctions', [$config['dql']['datetime']])
335
			->addSetup('setDefaultQueryHints', [$config['dql']['hints']])
336
			->addSetup('setNamingStrategy', CacheHelpers::filterArgs($config['namingStrategy']))
337
			->addSetup('setQuoteStrategy', CacheHelpers::filterArgs($config['quoteStrategy']))
338
			->addSetup('setEntityListenerResolver', CacheHelpers::filterArgs($config['entityListenerResolver']))
339
			->setAutowired(FALSE);
340
		/** @var Nette\DI\ServiceDefinition $configuration */
341
342
		$this->proxyAutoloaders[$config['proxyNamespace']] = $config['proxyDir'];
343
344
		$this->processSecondLevelCache($name, $config['secondLevelCache'], $isDefault);
345
346
		Validators::assertField($config, 'filters', 'array');
347
		foreach ($config['filters'] as $filterName => $filterClass) {
348
			$configuration->addSetup('addFilter', [$filterName, $filterClass]);
349
		}
350
351
		if ($config['targetEntityMappings']) {
352
			$configuration->addSetup('setTargetEntityMap', [array_map(function ($mapping) {
353
				return $mapping['targetEntity'];
354
			}, $config['targetEntityMappings'])]);
355
			$this->targetEntityMappings = Nette\Utils\Arrays::mergeTree($this->targetEntityMappings, $config['targetEntityMappings']);
356
		}
357
358
		if ($this->isKdybyEventsPresent()) {
359
			$builder->addDefinition($this->prefix($name . '.evm'))
360
				->setClass(Kdyby\Events\NamespacedEventManager::class, [Kdyby\Doctrine\Events::NS . '::'])
361
				->addSetup('$dispatchGlobalEvents', [TRUE]) // for BC
362
				->setAutowired(FALSE);
363
364
		} else {
365
			$builder->addDefinition($this->prefix($name . '.evm'))
366
				->setClass('Doctrine\Common\EventManager')
367
				->setAutowired(FALSE);
368
		}
369
370
		// entity manager
371
		$entityManager = $builder->addDefinition($managerServiceId = $this->prefix($name . '.entityManager'))
372
			->setClass(Kdyby\Doctrine\EntityManager::class)
373
			->setFactory(Kdyby\Doctrine\EntityManager::class . '::create', [
374
				$connectionService = $this->processConnection($name, $defaults, $isDefault),
375
				$this->prefix('@' . $name . '.ormConfiguration'),
376
				$this->prefix('@' . $name . '.evm'),
377
			])
378
			->addTag(self::TAG_ENTITY_MANAGER)
379
			->addTag('kdyby.doctrine.entityManager')
380
			->setAutowired($isDefault);
381
382
		if ($this->isTracyPresent()) {
383
			$entityManager->addSetup('?->bindEntityManager(?)', [$this->prefix('@' . $name . '.diagnosticsPanel'), '@self']);
384
		}
385
386
		$builder->addDefinition($this->prefix('repositoryFactory.' . $name . '.defaultRepositoryFactory'))
387
				->setClass($config['defaultRepositoryClassName'])
388
				->setImplement(IRepositoryFactory::class)
389
				->setArguments([new Code\PhpLiteral('$entityManager'), new Code\PhpLiteral('$classMetadata')])
390
				->setParameters([EntityManagerInterface::class . ' entityManager', Doctrine\ORM\Mapping\ClassMetadata::class . ' classMetadata'])
391
				->setAutowired(FALSE);
392
393
		$builder->addDefinition($this->prefix($name . '.schemaValidator'))
394
			->setClass(Doctrine\ORM\Tools\SchemaValidator::class, ['@' . $managerServiceId])
395
			->setAutowired($isDefault);
396
397
		$builder->addDefinition($this->prefix($name . '.schemaTool'))
398
			->setClass(Doctrine\ORM\Tools\SchemaTool::class, ['@' . $managerServiceId])
399
			->setAutowired($isDefault);
400
401
		$cacheCleaner = $builder->addDefinition($this->prefix($name . '.cacheCleaner'))
402
			->setClass(Kdyby\Doctrine\Tools\CacheCleaner::class, ['@' . $managerServiceId])
403
			->setAutowired($isDefault);
404
405
		$builder->addDefinition($this->prefix($name . '.schemaManager'))
406
			->setClass(AbstractSchemaManager::class)
407
			->setFactory('@' . Kdyby\Doctrine\Connection::class . '::getSchemaManager')
408
			->setAutowired($isDefault);
409
410
		foreach ($this->compiler->getExtensions(AnnotationsExtension::class) as $extension) {
411
			/** @var AnnotationsExtension $extension */
412
			$cacheCleaner->addSetup('addCacheStorage', [$extension->prefix('@cache.annotations')]);
413
		}
414
415
		if ($isDefault) {
416
			$builder->addDefinition($this->prefix('helper.entityManager'))
417
				->setClass(EntityManagerHelper::class, ['@' . $managerServiceId])
418
				->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...
419
420
			$builder->addDefinition($this->prefix('helper.connection'))
421
				->setClass(ConnectionHelper::class, [$connectionService])
422
				->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...
423
424
			$builder->addAlias($this->prefix('schemaValidator'), $this->prefix($name . '.schemaValidator'));
425
			$builder->addAlias($this->prefix('schemaTool'), $this->prefix($name . '.schemaTool'));
426
			$builder->addAlias($this->prefix('cacheCleaner'), $this->prefix($name . '.cacheCleaner'));
427
			$builder->addAlias($this->prefix('schemaManager'), $this->prefix($name . '.schemaManager'));
428
		}
429
430
		$this->configuredManagers[$name] = $managerServiceId;
431
		$this->managerConfigs[$name] = $config;
432
	}
433
434
435
436
	protected function processSecondLevelCache($name, array $config, $isDefault)
437
	{
438
		if (!$config['enabled']) {
439
			return;
440
		}
441
442
		$builder = $this->getContainerBuilder();
443
444
		$cacheFactory = $builder->addDefinition($this->prefix($name . '.cacheFactory'))
445
			->setClass(Doctrine\ORM\Cache\CacheFactory::class)
446
			->setFactory($config['factoryClass'], [
447
				$this->prefix('@' . $name . '.cacheRegionsConfiguration'),
448
				$this->processCache($config['driver'], $name . '.secondLevel'),
449
			])
450
			->setAutowired($isDefault);
451
452
		if ($config['factoryClass'] === $this->managerDefaults['secondLevelCache']['factoryClass']
453
			|| is_subclass_of($config['factoryClass'], $this->managerDefaults['secondLevelCache']['factoryClass'])
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if $this->managerDefaults['...Cache']['factoryClass'] can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
454
		) {
455
			$cacheFactory->addSetup('setFileLockRegionDirectory', [$config['fileLockRegionDirectory']]);
456
		}
457
458
		$builder->addDefinition($this->prefix($name . '.cacheRegionsConfiguration'))
459
			->setClass(Doctrine\ORM\Cache\RegionsConfiguration::class, [
460
				$config['regions']['defaultLifetime'],
461
				$config['regions']['defaultLockLifetime'],
462
			])
463
			->setAutowired($isDefault);
464
465
		$logger = $builder->addDefinition($this->prefix($name . '.cacheLogger'))
466
			->setClass(Doctrine\ORM\Cache\Logging\CacheLogger::class)
467
			->setFactory(Doctrine\ORM\Cache\Logging\CacheLoggerChain::class)
468
			->setAutowired(FALSE);
469
470
		if ($config['logging']) {
471
			$logger->addSetup('setLogger', [
472
				'statistics',
473
				new Statement(Doctrine\ORM\Cache\Logging\StatisticsCacheLogger::class)
474
			]);
475
		}
476
477
		$builder->addDefinition($cacheConfigName = $this->prefix($name . '.ormCacheConfiguration'))
478
			->setClass(Doctrine\ORM\Cache\CacheConfiguration::class)
479
			->addSetup('setCacheFactory', [$this->prefix('@' . $name . '.cacheFactory')])
480
			->addSetup('setCacheLogger', [$this->prefix('@' . $name . '.cacheLogger')])
481
			->setAutowired($isDefault);
482
483
		$configuration = $builder->getDefinition($this->prefix($name . '.ormConfiguration'));
484
		$configuration->addSetup('setSecondLevelCacheEnabled');
485
		$configuration->addSetup('setSecondLevelCacheConfiguration', ['@' . $cacheConfigName]);
486
	}
487
488
489
490
	protected function processConnection($name, array $defaults, $isDefault = FALSE)
491
	{
492
		$builder = $this->getContainerBuilder();
493
		$config = $this->resolveConfig($defaults, $this->connectionDefaults, $this->managerDefaults);
494
495
		if ($isDefault) {
496
			$builder->parameters[$this->name]['dbal']['defaultConnection'] = $name;
497
		}
498
499
		if (isset($defaults['connection'])) {
500
			return $this->prefix('@' . $defaults['connection'] . '.connection');
501
		}
502
503
		// config
504
		$configuration = $builder->addDefinition($this->prefix($name . '.dbalConfiguration'))
505
			->setClass(Doctrine\DBAL\Configuration::class)
506
			->addSetup('setResultCacheImpl', [$this->processCache($config['resultCache'], $name . '.dbalResult')])
507
			->addSetup('setSQLLogger', [new Statement(Doctrine\DBAL\Logging\LoggerChain::class)])
508
			->addSetup('setFilterSchemaAssetsExpression', [$config['schemaFilter']])
509
			->setAutowired(FALSE);
510
511
		// types
512
		Validators::assertField($config, 'types', 'array');
513
		$schemaTypes = $dbalTypes = [];
514
		foreach ($config['types'] as $dbType => $className) {
515
			$typeInst = Code\Helpers::createObject($className, []);
516
			/** @var Doctrine\DBAL\Types\Type $typeInst */
517
			$dbalTypes[$typeInst->getName()] = $className;
518
			$schemaTypes[$dbType] = $typeInst->getName();
519
		}
520
521
		// tracy panel
522
		if ($this->isTracyPresent()) {
523
			$builder->addDefinition($this->prefix($name . '.diagnosticsPanel'))
524
				->setClass(Kdyby\Doctrine\Diagnostics\Panel::class)
525
				->setAutowired(FALSE);
526
		}
527
528
		// connection
529
		$options = array_diff_key($config, array_flip(['types', 'resultCache', 'connection', 'logging']));
530
		$connection = $builder->addDefinition($connectionServiceId = $this->prefix($name . '.connection'))
531
			->setClass(Kdyby\Doctrine\Connection::class)
532
			->setFactory(Kdyby\Doctrine\Connection::class . '::create', [
533
				$options,
534
				$this->prefix('@' . $name . '.dbalConfiguration'),
535
				$this->prefix('@' . $name . '.evm'),
536
			])
537
			->addSetup('setSchemaTypes', [$schemaTypes])
538
			->addSetup('setDbalTypes', [$dbalTypes])
539
			->addTag(self::TAG_CONNECTION)
540
			->addTag('kdyby.doctrine.connection')
541
			->setAutowired($isDefault);
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::class, [$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) && strpos($driver, '@') === 0) { // service reference
580
			$metadataDriver->addSetup('addDriver', [$driver, $namespace]);
581
			return $driver;
582
		}
583
584
		if (is_string($driver) || is_array($driver)) {
585
			$paths = is_array($driver) ? $driver : [$driver];
586
			foreach ($paths as $path) {
587
				if (($pos = strrpos($path, '*')) !== FALSE) {
588
					$path = substr($path, 0, $pos);
589
				}
590
591
				if (!file_exists($path)) {
592
					throw new Nette\Utils\AssertionException("The metadata path expects to be an existing directory, $path given.");
593
				}
594
			}
595
596
			$driver = new Statement(self::ANNOTATION_DRIVER, is_array($paths) ? $paths : [$paths]);
597
		}
598
599
		$impl = $driver instanceof \stdClass ? $driver->value : ($driver instanceof Statement ? $driver->getEntity() : (string) $driver);
600
		list($driver) = CacheHelpers::filterArgs($driver);
601
		/** @var Statement $driver */
602
603
		/** @var string $impl */
604
		if (isset($this->metadataDriverClasses[$impl])) {
605
			$driver = new Statement($this->metadataDriverClasses[$impl], $driver->arguments);
606
		}
607
608
		if (is_string($driver->getEntity()) && substr($driver->getEntity(), 0, 1) === '@') {
609
			$metadataDriver->addSetup('addDriver', [$driver->getEntity(), $namespace]);
610
			return $driver->getEntity();
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::class)
624
			->setFactory($driver->getEntity(), $driver->arguments)
625
			->setAutowired(FALSE);
626
627
		$metadataDriver->addSetup('addDriver', ['@' . $serviceName, $namespace]);
628
		return '@' . $serviceName;
629
	}
630
631
632
633
	/**
634
	 * @param string|\stdClass $cache
635
	 * @param string $suffix
636
	 * @return string
637
	 */
638
	protected function processCache($cache, $suffix)
639
	{
640
		return CacheHelpers::processCache($this, $cache, $suffix, $this->getContainerBuilder()->parameters[$this->prefix('debug')]);
641
	}
642
643
644
645
	public function beforeCompile()
646
	{
647
		$this->processRepositories();
648
		$this->processEventManagers();
649
	}
650
651
652
653
	protected function processRepositories()
654
	{
655
		$builder = $this->getContainerBuilder();
656
657
		$disabled = TRUE;
658
		foreach ($this->configuredManagers as $managerName => $_) {
659
			$factoryClassName = $builder->getDefinition($this->prefix($managerName . '.repositoryFactory'))->getClass();
660
			if ($factoryClassName === Kdyby\Doctrine\RepositoryFactory::class || in_array(Kdyby\Doctrine\RepositoryFactory::class, class_parents($factoryClassName), TRUE)) {
661
				$disabled = FALSE;
662
			}
663
		}
664
665
		if ($disabled) {
666
			return;
667
		}
668
669
		if (!method_exists($builder, 'findByType')) {
670
			foreach ($this->configuredManagers as $managerName => $_) {
671
				$builder->getDefinition($this->prefix($managerName . '.repositoryFactory'))
672
					->addSetup('setServiceIdsMap', [[], $this->prefix('repositoryFactory.' . $managerName . '.defaultRepositoryFactory')]);
673
			}
674
675
			return;
676
		}
677
678
		$serviceMap = array_fill_keys(array_keys($this->configuredManagers), []);
679
		foreach ($builder->findByType(Doctrine\ORM\EntityRepository::class) as $originalServiceName => $originalDef) {
680
			if (is_string($originalDef)) {
681
				$originalServiceName = $originalDef;
682
				$originalDef = $builder->getDefinition($originalServiceName);
683
			}
684
685
			if (strpos($originalServiceName, $this->name . '.') === 0) {
686
				continue; // ignore
687
			}
688
689
			$originalDefFactory = $originalDef->getFactory();
690
			$factory = ($originalDefFactory !== NULL) ? $originalDefFactory->getEntity() : $originalDef->getClass();
691
			if (stripos($factory, '::getRepository') !== FALSE) {
692
				continue; // ignore
693
			}
694
695
			$factoryServiceName = $this->prefix('repositoryFactory.' . $originalServiceName);
696
			$factoryDef = $builder->addDefinition($factoryServiceName, $originalDef)
697
				->setImplement(IRepositoryFactory::class)
698
				->setParameters([Doctrine\ORM\EntityManagerInterface::class . ' entityManager', Doctrine\ORM\Mapping\ClassMetadata::class . ' classMetadata'])
699
				->setAutowired(FALSE);
700
			$factoryStatement = $factoryDef->getFactory() ?: new Statement($factoryDef->getClass());
701
			$factoryStatement->arguments[0] = new Code\PhpLiteral('$entityManager');
702
			$factoryStatement->arguments[1] = new Code\PhpLiteral('$classMetadata');
703
			$factoryDef->setArguments($factoryStatement->arguments);
704
705
			$boundManagers = $this->getServiceBoundManagers($originalDef);
706
			Validators::assert($boundManagers, 'list:1', 'bound manager');
707
708
			if ($boundEntity = $originalDef->getTag(self::TAG_REPOSITORY_ENTITY)) {
709
				if (!is_string($boundEntity) || !class_exists($boundEntity)) {
710
					throw new Nette\Utils\AssertionException(sprintf('The entity "%s" for repository "%s" cannot be autoloaded.', $boundEntity, $originalDef->getClass()));
711
				}
712
				$entityArgument = $boundEntity;
713
714
			} else {
715
				throw new Nette\Utils\AssertionException(sprintf(
716
					'The magic auto-detection of entity for repository %s for %s was removed from Kdyby.' .
717
					'You have to specify %s tag with classname of the related entity in order to use this feature.',
718
					$originalDef->getClass(),
719
					IRepositoryFactory::class,
720
					self::TAG_REPOSITORY_ENTITY
721
				));
722
			}
723
724
			$builder->removeDefinition($originalServiceName);
725
			$builder->addDefinition($originalServiceName)
726
				->setClass($originalDef->getClass())
727
				->setFactory(sprintf('@%s::getRepository', $this->configuredManagers[$boundManagers[0]]), [$entityArgument]);
728
729
			$serviceMap[$boundManagers[0]][$originalDef->getClass()] = $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
	protected function processEventManagers()
744
	{
745
		$builder = $this->getContainerBuilder();
746
		$customEvmService = $builder->getByType(\Doctrine\Common\EventManager::class);
747
		if ($this->isKdybyEventsPresent() || !$customEvmService) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $customEvmService of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

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

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

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

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
748
			return;
749
		}
750
751
		foreach ($this->configuredManagers as $managerName => $_) {
752
			$builder->getDefinition($this->prefix($managerName . '.evm'))->setFactory('@' . $customEvmService);
753
		}
754
	}
755
756
757
758
	/**
759
	 * @param Nette\DI\ServiceDefinition $def
760
	 * @return string[]
761
	 */
762
	protected function getServiceBoundManagers(Nette\DI\ServiceDefinition $def)
763
	{
764
		$builder = $this->getContainerBuilder();
765
		$boundManagers = $def->getTag(self::TAG_BIND_TO_MANAGER);
766
767
		return is_array($boundManagers) ? $boundManagers : [$builder->parameters[$this->name]['orm']['defaultEntityManager']];
768
	}
769
770
771
772
	public function afterCompile(Code\ClassType $class)
773
	{
774
		$init = $class->getMethod('initialize');
775
776
		if ($this->isTracyPresent()) {
777
			$init->addBody('?::registerBluescreen($this);', [new Code\PhpLiteral(Kdyby\Doctrine\Diagnostics\Panel::class)]);
778
			$this->addCollapsePathsToTracy($init);
779
		}
780
781
		foreach ($this->proxyAutoloaders as $namespace => $dir) {
782
			$originalInitialize = $init->getBody();
783
			$init->setBody('?::create(?, ?)->register();', [new Code\PhpLiteral(Kdyby\Doctrine\Proxy\ProxyAutoloader::class), $dir, $namespace]);
784
			$init->addBody((string) $originalInitialize);
785
		}
786
	}
787
788
789
790
	/**
791
	 * @param $provided
792
	 * @param $defaults
793
	 * @param $diff
794
	 * @return array
795
	 */
796
	private function resolveConfig(array $provided, array $defaults, array $diff = [])
797
	{
798
		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...
799
			array_diff_key($provided, array_diff_key($diff, $defaults)),
800
			$defaults
801
		));
802
	}
803
804
805
	/**
806
	 * @param array $targetEntityMappings
807
	 * @return array
808
	 */
809
	private function normalizeTargetEntityMappings(array $targetEntityMappings)
810
	{
811
		$normalized = [];
812
		foreach ($targetEntityMappings as $originalEntity => $targetEntity) {
813
			$originalEntity = ltrim($originalEntity, '\\');
814
			Validators::assert($targetEntity, 'array|string');
815
			if (is_array($targetEntity)) {
816
				Validators::assertField($targetEntity, 'targetEntity', 'string');
817
				$mapping = array_merge($targetEntity, [
818
					'targetEntity' => ltrim($targetEntity['targetEntity'], '\\')
819
				]);
820
821
			} else {
822
				$mapping = [
823
					'targetEntity' => ltrim($targetEntity, '\\'),
824
				];
825
			}
826
			$normalized[$originalEntity] = $mapping;
827
		}
828
		return $normalized;
829
	}
830
831
832
833
	/**
834
	 * @return bool
835
	 */
836
	private function isTracyPresent()
837
	{
838
		return interface_exists(\Tracy\IBarPanel::class);
839
	}
840
841
842
843
	/**
844
	 * @return bool
845
	 */
846
	private function isKdybyEventsPresent()
847
	{
848
		return (bool) $this->compiler->getExtensions(\Kdyby\Events\DI\EventsExtension::class);
849
	}
850
851
852
853
	private function addCollapsePathsToTracy(Method $init)
854
	{
855
		$blueScreen = \Tracy\Debugger::class . '::getBlueScreen()';
856
		$commonDirname = dirname(Nette\Reflection\ClassType::from(Doctrine\Common\Version::class)->getFileName());
857
858
		$init->addBody($blueScreen . '->collapsePaths[] = ?;', [dirname(Nette\Reflection\ClassType::from(Kdyby\Doctrine\Exception::class)->getFileName())]);
859
		$init->addBody($blueScreen . '->collapsePaths[] = ?;', [dirname(dirname(dirname(dirname($commonDirname))))]); // this should be vendor/doctrine
860
		foreach ($this->proxyAutoloaders as $dir) {
861
			$init->addBody($blueScreen . '->collapsePaths[] = ?;', [$dir]);
862
		}
863
	}
864
865
866
867
	/**
868
	 * @param \Nette\Configurator $configurator
869
	 */
870
	public static function register(Nette\Configurator $configurator)
871
	{
872
		$configurator->onCompile[] = function ($config, Nette\DI\Compiler $compiler) {
873
			$compiler->addExtension('doctrine', new OrmExtension());
874
		};
875
	}
876
877
878
879
	/**
880
	 * @param array $array
881
	 */
882
	private static function natSortKeys(array &$array)
883
	{
884
		$keys = array_keys($array);
885
		natsort($keys);
886
		$keys = array_flip(array_reverse($keys, TRUE));
887
		$array = array_merge($keys, $array);
888
		return $array;
889
	}
890
891
}
892