Completed
Push — master ( 33d1bc...d45ae6 )
by Filip
03:06
created

OrmExtension::loadConsole()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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