Completed
Push — master ( 53a95c...9c3abf )
by Jáchym
07:51
created

OrmExtension::loadConsole()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
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 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
		'schemaFilter' => NULL,
104
	];
105
106
	/**
107
	 * @var array
108
	 */
109
	public $metadataDriverClasses = [
110
		self::ANNOTATION_DRIVER => Doctrine\ORM\Mapping\Driver\AnnotationDriver::class,
111
		'static' => Doctrine\Common\Persistence\Mapping\Driver\StaticPHPDriver::class,
112
		'yml' => Doctrine\ORM\Mapping\Driver\YamlDriver::class,
113
		'yaml' => Doctrine\ORM\Mapping\Driver\YamlDriver::class,
114
		'xml' => Doctrine\ORM\Mapping\Driver\XmlDriver::class,
115
		'db' => Doctrine\ORM\Mapping\Driver\DatabaseDriver::class,
116
	];
117
118
	/**
119
	 * @var array
120
	 */
121
	private $proxyAutoloaders = [];
122
123
	/**
124
	 * @var array
125
	 */
126
	private $targetEntityMappings = [];
127
128
	/**
129
	 * @var array
130
	 */
131
	private $configuredManagers = [];
132
133
	/**
134
	 * @var array
135
	 */
136
	private $managerConfigs = [];
137
138
	/**
139
	 * @var array
140
	 */
141
	private $configuredConnections = [];
142
143
144
145
	public function loadConfiguration()
146
	{
147
		$this->proxyAutoloaders =
148
		$this->targetEntityMappings =
149
		$this->configuredConnections =
150
		$this->managerConfigs =
151
		$this->configuredManagers = [];
152
153
		if (!$this->compiler->getExtensions(AnnotationsExtension::class)) {
154
			throw new Nette\Utils\AssertionException(sprintf("You should register %s before %s.", AnnotationsExtension::class, get_class($this)));
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
			/** @var mixed[] $emConfig */
183
			$emConfig = Nette\DI\Config\Helpers::merge($emConfig, $defaults);
184
			$this->processEntityManager($name, $emConfig);
185
		}
186
187
		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...
188
			if (!$this->isKdybyEventsPresent()) {
189
				throw new Nette\Utils\AssertionException('The option \'targetEntityMappings\' requires \'Kdyby\Events\DI\EventsExtension\'.', E_USER_NOTICE);
190
			}
191
192
			$listener = $builder->addDefinition($this->prefix('resolveTargetEntityListener'))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::setClass() has been deprecated.

This method has been deprecated.

Loading history...
193
				->setClass(Kdyby\Doctrine\Tools\ResolveTargetEntityListener::class)
194
				->addTag(Kdyby\Events\DI\EventsExtension::TAG_SUBSCRIBER);
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'))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::setClass() has been deprecated.

This method has been deprecated.

Loading history...
204
			->setClass(Kdyby\Doctrine\Registry::class, [
205
				$this->configuredConnections,
206
				$this->configuredManagers,
207
				$builder->parameters[$this->name]['dbal']['defaultConnection'],
208
				$builder->parameters[$this->name]['orm']['defaultEntityManager'],
209
			]);
210
	}
211
212
213
214
	protected function loadConsole()
215
	{
216
		$builder = $this->getContainerBuilder();
217
218
		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...
219
			$cli = $builder->addDefinition($this->prefix('cli.' . $i))
220
				->addTag(Kdyby\Console\DI\ConsoleExtension::TAG_COMMAND)
221
				->addTag(Nette\DI\Extensions\InjectExtension::TAG_INJECT, FALSE); // lazy injects
222
223
			if (is_string($command)) {
224
				$cli->setClass($command);
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::setClass() has been deprecated.

This method has been deprecated.

Loading history...
225
226
			} else {
227
				throw new Kdyby\Doctrine\NotSupportedException;
228
			}
229
		}
230
	}
231
232
233
234
	protected function processEntityManager($name, array $defaults)
235
	{
236
		$builder = $this->getContainerBuilder();
237
		$config = $this->resolveConfig($defaults, $this->managerDefaults, $this->connectionDefaults);
238
239
		if ($isDefault = !isset($builder->parameters[$this->name]['orm']['defaultEntityManager'])) {
240
			$builder->parameters[$this->name]['orm']['defaultEntityManager'] = $name;
241
		}
242
243
		$metadataDriver = $builder->addDefinition($this->prefix($name . '.metadataDriver'))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::setClass() has been deprecated.

This method has been deprecated.

Loading history...
244
			->setClass(Doctrine\Common\Persistence\Mapping\Driver\MappingDriverChain::class)
245
			->setAutowired(FALSE);
246
		/** @var \Nette\DI\ServiceDefinition $metadataDriver */
247
248
		Validators::assertField($config, 'metadata', 'array');
249
		Validators::assertField($config, 'targetEntityMappings', 'array');
250
		$config['targetEntityMappings'] = $this->normalizeTargetEntityMappings($config['targetEntityMappings']);
251
		foreach ($this->compiler->getExtensions() as $extension) {
252
			if ($extension instanceof IEntityProvider) {
253
				$metadata = $extension->getEntityMappings();
254
				Validators::assert($metadata, 'array');
255
				foreach ($metadata as $namespace => $nsConfig) {
256
					if (array_key_exists($namespace, $config['metadata'])) {
257
						throw new Nette\Utils\AssertionException(sprintf('The namespace %s is already configured, provider cannot change it', $namespace));
258
					}
259
					$config['metadata'][$namespace] = $nsConfig;
260
				}
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, self::KDYBY_METADATA_NAMESPACE, __DIR__ . '/../Entities', $name);
286
287
		if (empty($config['metadata'])) {
288
			$metadataDriver->addSetup('setDefaultDriver', [
289
				new Statement($this->metadataDriverClasses[self::ANNOTATION_DRIVER], [
290
					'@' . Doctrine\Common\Annotations\Reader::class,
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
				])
293
			]);
294
		}
295
296
		if ($config['repositoryFactoryClassName'] === 'default') {
297
			$config['repositoryFactoryClassName'] = DefaultRepositoryFactory::class;
298
		}
299
		$builder->addDefinition($this->prefix($name . '.repositoryFactory'))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::setClass() has been deprecated.

This method has been deprecated.

Loading history...
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_FILE_NOT_EXISTS)
313
			: $config['autoGenerateProxyClasses'];
314
315
		$configuration = $builder->addDefinition($this->prefix($name . '.ormConfiguration'))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::setClass() has been deprecated.

This method has been deprecated.

Loading history...
316
			->setClass(Kdyby\Doctrine\Configuration::class)
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
		/** @var Nette\DI\ServiceDefinition $configuration */
340
341
		$this->proxyAutoloaders[$config['proxyNamespace']] = $config['proxyDir'];
342
343
		$this->processSecondLevelCache($name, $config['secondLevelCache'], $isDefault);
344
345
		Validators::assertField($config, 'filters', 'array');
346
		foreach ($config['filters'] as $filterName => $filterClass) {
347
			$configuration->addSetup('addFilter', [$filterName, $filterClass]);
348
		}
349
350
		if ($config['targetEntityMappings']) {
351
			$configuration->addSetup('setTargetEntityMap', [array_map(function ($mapping) {
352
				return $mapping['targetEntity'];
353
			}, $config['targetEntityMappings'])]);
354
			$this->targetEntityMappings = Nette\Utils\Arrays::mergeTree($this->targetEntityMappings, $config['targetEntityMappings']);
355
		}
356
357
		if ($this->isKdybyEventsPresent()) {
358
			$builder->addDefinition($this->prefix($name . '.evm'))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::setClass() has been deprecated.

This method has been deprecated.

Loading history...
359
				->setClass(Kdyby\Events\NamespacedEventManager::class, [Kdyby\Doctrine\Events::NS . '::'])
360
				->addSetup('$dispatchGlobalEvents', [TRUE]) // for BC
361
				->setAutowired(FALSE);
362
363
		} else {
364
			$builder->addDefinition($this->prefix($name . '.evm'))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::setClass() has been deprecated.

This method has been deprecated.

Loading history...
365
				->setClass('Doctrine\Common\EventManager')
366
				->setAutowired(FALSE);
367
		}
368
369
		// entity manager
370
		$entityManager = $builder->addDefinition($managerServiceId = $this->prefix($name . '.entityManager'))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::setClass() has been deprecated.

This method has been deprecated.

Loading history...
371
			->setClass(Kdyby\Doctrine\EntityManager::class)
372
			->setFactory(Kdyby\Doctrine\EntityManager::class . '::create', [
373
				$connectionService = $this->processConnection($name, $defaults, $isDefault),
374
				$this->prefix('@' . $name . '.ormConfiguration'),
375
				$this->prefix('@' . $name . '.evm'),
376
			])
377
			->addTag(self::TAG_ENTITY_MANAGER)
378
			->addTag('kdyby.doctrine.entityManager')
379
			->setAutowired($isDefault);
380
381
		if ($this->isTracyPresent()) {
382
			$entityManager->addSetup('?->bindEntityManager(?)', [$this->prefix('@' . $name . '.diagnosticsPanel'), '@self']);
383
		}
384
385
		$builder->addDefinition($this->prefix('repositoryFactory.' . $name . '.defaultRepositoryFactory'))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::setClass() has been deprecated.

This method has been deprecated.

Loading history...
386
				->setClass($config['defaultRepositoryClassName'])
387
				->setImplement(IRepositoryFactory::class)
388
				->setArguments([new Code\PhpLiteral('$entityManager'), new Code\PhpLiteral('$classMetadata')])
389
				->setParameters([EntityManagerInterface::class . ' entityManager', Doctrine\ORM\Mapping\ClassMetadata::class . ' classMetadata'])
390
				->setAutowired(FALSE);
391
392
		$builder->addDefinition($this->prefix($name . '.schemaValidator'))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::setClass() has been deprecated.

This method has been deprecated.

Loading history...
393
			->setClass(Doctrine\ORM\Tools\SchemaValidator::class, ['@' . $managerServiceId])
394
			->setAutowired($isDefault);
395
396
		$builder->addDefinition($this->prefix($name . '.schemaTool'))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::setClass() has been deprecated.

This method has been deprecated.

Loading history...
397
			->setClass(Doctrine\ORM\Tools\SchemaTool::class, ['@' . $managerServiceId])
398
			->setAutowired($isDefault);
399
400
		$cacheCleaner = $builder->addDefinition($this->prefix($name . '.cacheCleaner'))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::setClass() has been deprecated.

This method has been deprecated.

Loading history...
401
			->setClass(Kdyby\Doctrine\Tools\CacheCleaner::class, ['@' . $managerServiceId])
402
			->setAutowired($isDefault);
403
404
		$builder->addDefinition($this->prefix($name . '.schemaManager'))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::setClass() has been deprecated.

This method has been deprecated.

Loading history...
405
			->setClass(AbstractSchemaManager::class)
406
			->setFactory('@' . Kdyby\Doctrine\Connection::class . '::getSchemaManager')
407
			->setAutowired($isDefault);
408
409
		foreach ($this->compiler->getExtensions(AnnotationsExtension::class) as $extension) {
410
			/** @var AnnotationsExtension $extension */
411
			$cacheCleaner->addSetup('addCacheStorage', [$extension->prefix('@cache.annotations')]);
412
		}
413
414
		if ($isDefault) {
415
			$builder->addDefinition($this->prefix('helper.entityManager'))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::setClass() has been deprecated.

This method has been deprecated.

Loading history...
416
				->setClass(EntityManagerHelper::class, ['@' . $managerServiceId])
417
				->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...
418
419
			$builder->addDefinition($this->prefix('helper.connection'))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::setClass() has been deprecated.

This method has been deprecated.

Loading history...
420
				->setClass(ConnectionHelper::class, [$connectionService])
421
				->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...
422
423
			$builder->addAlias($this->prefix('schemaValidator'), $this->prefix($name . '.schemaValidator'));
424
			$builder->addAlias($this->prefix('schemaTool'), $this->prefix($name . '.schemaTool'));
425
			$builder->addAlias($this->prefix('cacheCleaner'), $this->prefix($name . '.cacheCleaner'));
426
			$builder->addAlias($this->prefix('schemaManager'), $this->prefix($name . '.schemaManager'));
427
		}
428
429
		$this->configuredManagers[$name] = $managerServiceId;
430
		$this->managerConfigs[$name] = $config;
431
	}
432
433
434
435
	protected function processSecondLevelCache($name, array $config, $isDefault)
436
	{
437
		if (!$config['enabled']) {
438
			return;
439
		}
440
441
		$builder = $this->getContainerBuilder();
442
443
		$cacheFactory = $builder->addDefinition($this->prefix($name . '.cacheFactory'))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::setClass() has been deprecated.

This method has been deprecated.

Loading history...
444
			->setClass(Doctrine\ORM\Cache\CacheFactory::class)
445
			->setFactory($config['factoryClass'], [
446
				$this->prefix('@' . $name . '.cacheRegionsConfiguration'),
447
				$this->processCache($config['driver'], $name . '.secondLevel'),
448
			])
449
			->setAutowired($isDefault);
450
451
		if ($config['factoryClass'] === $this->managerDefaults['secondLevelCache']['factoryClass']
452
			|| 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...
453
		) {
454
			$cacheFactory->addSetup('setFileLockRegionDirectory', [$config['fileLockRegionDirectory']]);
455
		}
456
457
		$builder->addDefinition($this->prefix($name . '.cacheRegionsConfiguration'))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::setClass() has been deprecated.

This method has been deprecated.

Loading history...
458
			->setClass(Doctrine\ORM\Cache\RegionsConfiguration::class, [
459
				$config['regions']['defaultLifetime'],
460
				$config['regions']['defaultLockLifetime'],
461
			])
462
			->setAutowired($isDefault);
463
464
		$logger = $builder->addDefinition($this->prefix($name . '.cacheLogger'))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::setClass() has been deprecated.

This method has been deprecated.

Loading history...
465
			->setClass(Doctrine\ORM\Cache\Logging\CacheLogger::class)
466
			->setFactory(Doctrine\ORM\Cache\Logging\CacheLoggerChain::class)
467
			->setAutowired(FALSE);
468
469
		if ($config['logging']) {
470
			$logger->addSetup('setLogger', [
471
				'statistics',
472
				new Statement(Doctrine\ORM\Cache\Logging\StatisticsCacheLogger::class)
473
			]);
474
		}
475
476
		$builder->addDefinition($cacheConfigName = $this->prefix($name . '.ormCacheConfiguration'))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::setClass() has been deprecated.

This method has been deprecated.

Loading history...
477
			->setClass(Doctrine\ORM\Cache\CacheConfiguration::class)
478
			->addSetup('setCacheFactory', [$this->prefix('@' . $name . '.cacheFactory')])
479
			->addSetup('setCacheLogger', [$this->prefix('@' . $name . '.cacheLogger')])
480
			->setAutowired($isDefault);
481
482
		$configuration = $builder->getDefinition($this->prefix($name . '.ormConfiguration'));
483
		$configuration->addSetup('setSecondLevelCacheEnabled');
484
		$configuration->addSetup('setSecondLevelCacheConfiguration', ['@' . $cacheConfigName]);
485
	}
486
487
488
489
	protected function processConnection($name, array $defaults, $isDefault = FALSE)
490
	{
491
		$builder = $this->getContainerBuilder();
492
		$config = $this->resolveConfig($defaults, $this->connectionDefaults, $this->managerDefaults);
493
494
		if ($isDefault) {
495
			$builder->parameters[$this->name]['dbal']['defaultConnection'] = $name;
496
		}
497
498
		if (isset($defaults['connection'])) {
499
			return $this->prefix('@' . $defaults['connection'] . '.connection');
500
		}
501
502
		// config
503
		$configuration = $builder->addDefinition($this->prefix($name . '.dbalConfiguration'))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::setClass() has been deprecated.

This method has been deprecated.

Loading history...
504
			->setClass(Doctrine\DBAL\Configuration::class)
505
			->addSetup('setResultCacheImpl', [$this->processCache($config['resultCache'], $name . '.dbalResult')])
506
			->addSetup('setSQLLogger', [new Statement(Doctrine\DBAL\Logging\LoggerChain::class)])
507
			->addSetup('setFilterSchemaAssetsExpression', [$config['schemaFilter']])
508
			->setAutowired(FALSE);
509
510
		// types
511
		Validators::assertField($config, 'types', 'array');
512
		$schemaTypes = $dbalTypes = [];
513
		foreach ($config['types'] as $dbType => $className) {
514
			/** @var Doctrine\DBAL\Types\Type $typeInst */
515
			$typeInst = Code\Helpers::createObject($className, []);
516
			$dbalTypes[$typeInst->getName()] = $className;
517
			$schemaTypes[$dbType] = $typeInst->getName();
518
		}
519
520
		// tracy panel
521
		if ($this->isTracyPresent()) {
522
			$builder->addDefinition($this->prefix($name . '.diagnosticsPanel'))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::setClass() has been deprecated.

This method has been deprecated.

Loading history...
523
				->setClass(Kdyby\Doctrine\Diagnostics\Panel::class)
524
				->setAutowired(FALSE);
525
		}
526
527
		// connection
528
		$options = array_diff_key($config, array_flip(['types', 'resultCache', 'connection', 'logging']));
529
		$connection = $builder->addDefinition($connectionServiceId = $this->prefix($name . '.connection'))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::setClass() has been deprecated.

This method has been deprecated.

Loading history...
530
			->setClass(Kdyby\Doctrine\Connection::class)
531
			->setFactory(Kdyby\Doctrine\Connection::class . '::create', [
532
				$options,
533
				$this->prefix('@' . $name . '.dbalConfiguration'),
534
				$this->prefix('@' . $name . '.evm'),
535
			])
536
			->addSetup('setSchemaTypes', [$schemaTypes])
537
			->addSetup('setDbalTypes', [$dbalTypes])
538
			->addTag(self::TAG_CONNECTION)
539
			->addTag('kdyby.doctrine.connection')
540
			->setAutowired($isDefault);
541
542
		if ($this->isTracyPresent()) {
543
			$connection->addSetup('$panel = ?->bindConnection(?)', [$this->prefix('@' . $name . '.diagnosticsPanel'), '@self']);
544
		}
545
546
		/** @var Nette\DI\ServiceDefinition $connection */
547
548
		$this->configuredConnections[$name] = $connectionServiceId;
549
550
		if (!is_bool($config['logging'])) {
551
			$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...
552
			$configuration->addSetup('$service->getSQLLogger()->addLogger(?)', [$fileLogger]);
553
554
		} elseif ($config['logging']) {
555
			$connection->addSetup('?->enableLogging()', [new Code\PhpLiteral('$panel')]);
556
		}
557
558
		return $this->prefix('@' . $name . '.connection');
559
	}
560
561
562
563
	/**
564
	 * @param \Nette\DI\ServiceDefinition $metadataDriver
565
	 * @param string $namespace
566
	 * @param string|array|\stdClass $driver
567
	 * @param string $prefix
568
	 * @throws \Nette\Utils\AssertionException
569
	 * @return string
570
	 */
571
	protected function processMetadataDriver(Nette\DI\ServiceDefinition $metadataDriver, $namespace, $driver, $prefix)
572
	{
573
		if (!is_string($namespace) || !Strings::match($namespace, '#^' . self::PHP_NAMESPACE . '\z#')) {
574
			throw new Nette\Utils\AssertionException("The metadata namespace expects to be valid namespace, $namespace given.");
575
		}
576
		$namespace = ltrim($namespace, '\\');
577
578
		if (is_string($driver) && strpos($driver, '@') === 0) { // service reference
579
			$metadataDriver->addSetup('addDriver', [$driver, $namespace]);
580
			return $driver;
581
		}
582
583
		if (is_string($driver) || is_array($driver)) {
584
			$paths = is_array($driver) ? $driver : [$driver];
585
			foreach ($paths as $path) {
586
				if (!file_exists($path)) {
587
					throw new Nette\Utils\AssertionException("The metadata path expects to be an existing directory, $path given.");
588
				}
589
			}
590
591
			$driver = new Statement(self::ANNOTATION_DRIVER, is_array($paths) ? $paths : [$paths]);
592
		}
593
594
		$impl = $driver instanceof \stdClass ? $driver->value : ($driver instanceof Statement ? $driver->getEntity() : (string) $driver);
595
		list($driver) = CacheHelpers::filterArgs($driver);
596
		/** @var Statement $driver */
597
598
		/** @var string $impl */
599
		if (isset($this->metadataDriverClasses[$impl])) {
600
			$driver = new Statement($this->metadataDriverClasses[$impl], $driver->arguments);
601
		}
602
603
		if (is_string($driver->getEntity()) && substr($driver->getEntity(), 0, 1) === '@') {
604
			$metadataDriver->addSetup('addDriver', [$driver->getEntity(), $namespace]);
605
			return $driver->getEntity();
606
		}
607
608
		if ($impl === self::ANNOTATION_DRIVER) {
609
			$driver->arguments = [
610
				'@' . Doctrine\Common\Annotations\Reader::class,
611
				Nette\Utils\Arrays::flatten($driver->arguments)
612
			];
613
		}
614
615
		$serviceName = $this->prefix($prefix . '.driver.' . str_replace('\\', '_', $namespace) . '.' . str_replace('\\', '_', $impl) . 'Impl');
616
617
		$this->getContainerBuilder()->addDefinition($serviceName)
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::setClass() has been deprecated.

This method has been deprecated.

Loading history...
618
			->setClass(Doctrine\Common\Persistence\Mapping\Driver\MappingDriver::class)
619
			->setFactory($driver->getEntity(), $driver->arguments)
620
			->setAutowired(FALSE);
621
622
		$metadataDriver->addSetup('addDriver', ['@' . $serviceName, $namespace]);
623
		return '@' . $serviceName;
624
	}
625
626
627
628
	/**
629
	 * @param string|\stdClass $cache
630
	 * @param string $suffix
631
	 * @return string
632
	 */
633
	protected function processCache($cache, $suffix)
634
	{
635
		return CacheHelpers::processCache($this, $cache, $suffix, $this->getContainerBuilder()->parameters[$this->prefix('debug')]);
636
	}
637
638
639
640
	public function beforeCompile()
641
	{
642
		$this->processRepositories();
643
		$this->processEventManagers();
644
	}
645
646
647
648
	protected function processRepositories()
649
	{
650
		$builder = $this->getContainerBuilder();
651
652
		$disabled = TRUE;
653
		foreach ($this->configuredManagers as $managerName => $_) {
654
			$factoryClassName = $builder->getDefinition($this->prefix($managerName . '.repositoryFactory'))->getClass();
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::getClass() has been deprecated.

This method has been deprecated.

Loading history...
655
			if ($factoryClassName === Kdyby\Doctrine\RepositoryFactory::class || in_array(Kdyby\Doctrine\RepositoryFactory::class, class_parents($factoryClassName), TRUE)) {
656
				$disabled = FALSE;
657
			}
658
		}
659
660
		if ($disabled) {
661
			return;
662
		}
663
664
		if (!method_exists($builder, 'findByType')) {
665
			foreach ($this->configuredManagers as $managerName => $_) {
666
				$builder->getDefinition($this->prefix($managerName . '.repositoryFactory'))
667
					->addSetup('setServiceIdsMap', [[], $this->prefix('repositoryFactory.' . $managerName . '.defaultRepositoryFactory')]);
668
			}
669
670
			return;
671
		}
672
673
		$serviceMap = array_fill_keys(array_keys($this->configuredManagers), []);
674
		foreach ($builder->findByType(Doctrine\ORM\EntityRepository::class) as $originalServiceName => $originalDef) {
675
			if (strpos($originalServiceName, $this->name . '.') === 0) {
676
				continue; // ignore
677
			}
678
679
			$originalDefFactory = $originalDef->getFactory();
680
			$factory = ($originalDefFactory !== NULL) ? $originalDefFactory->getEntity() : $originalDef->getClass();
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::getClass() has been deprecated.

This method has been deprecated.

Loading history...
681
			if (stripos($factory, '::getRepository') !== FALSE) {
682
				continue; // ignore
683
			}
684
685
			$factoryServiceName = $this->prefix('repositoryFactory.' . $originalServiceName);
686
			$factoryDef = $builder->addDefinition($factoryServiceName, $originalDef)
687
				->setImplement(IRepositoryFactory::class)
688
				->setParameters([Doctrine\ORM\EntityManagerInterface::class . ' entityManager', Doctrine\ORM\Mapping\ClassMetadata::class . ' classMetadata'])
689
				->setAutowired(FALSE);
690
			$factoryStatement = $factoryDef->getFactory() ?: new Statement($factoryDef->getClass());
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::getClass() has been deprecated.

This method has been deprecated.

Loading history...
691
			$factoryStatement->arguments[0] = new Code\PhpLiteral('$entityManager');
692
			$factoryStatement->arguments[1] = new Code\PhpLiteral('$classMetadata');
693
			$factoryDef->setArguments($factoryStatement->arguments);
694
695
			$boundManagers = $this->getServiceBoundManagers($originalDef);
696
			Validators::assert($boundManagers, 'list:1', 'bound manager');
697
698
			if ($boundEntity = $originalDef->getTag(self::TAG_REPOSITORY_ENTITY)) {
699
				if (!is_string($boundEntity) || !class_exists($boundEntity)) {
700
					throw new Nette\Utils\AssertionException(sprintf('The entity "%s" for repository "%s" cannot be autoloaded.', $boundEntity, $originalDef->getClass()));
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::getClass() has been deprecated.

This method has been deprecated.

Loading history...
701
				}
702
				$entityArgument = $boundEntity;
703
704
			} else {
705
				throw new Nette\Utils\AssertionException(sprintf(
706
					'The magic auto-detection of entity for repository %s for %s was removed from Kdyby.' .
707
					'You have to specify %s tag with classname of the related entity in order to use this feature.',
708
					$originalDef->getClass(),
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::getClass() has been deprecated.

This method has been deprecated.

Loading history...
709
					IRepositoryFactory::class,
710
					self::TAG_REPOSITORY_ENTITY
711
				));
712
			}
713
714
			$builder->removeDefinition($originalServiceName);
715
			$builder->addDefinition($originalServiceName)
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::setClass() has been deprecated.

This method has been deprecated.

Loading history...
716
				->setClass($originalDef->getClass())
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::getClass() has been deprecated.

This method has been deprecated.

Loading history...
717
				->setFactory(sprintf('@%s::getRepository', $this->configuredManagers[$boundManagers[0]]), [$entityArgument]);
718
719
			$serviceMap[$boundManagers[0]][$originalDef->getClass()] = $factoryServiceName;
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::getClass() has been deprecated.

This method has been deprecated.

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