Completed
Pull Request — master (#274)
by
unknown
17:41 queued 14:28
created

OrmExtension::addCollapsePathsToTracy()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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