Passed
Push — master ( b562f8...22d897 )
by Tomáš
03:37
created

DoctrineExtension::loadConfiguration()   C

Complexity

Conditions 10
Paths 256

Size

Total Lines 99
Code Lines 64

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 57
CRAP Score 10.1865

Importance

Changes 10
Bugs 2 Features 2
Metric Value
cc 10
eloc 64
c 10
b 2
f 2
nc 256
nop 0
dl 0
loc 99
ccs 57
cts 65
cp 0.8769
crap 10.1865
rs 5.6921

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php declare(strict_types = 1);
2
3
namespace Portiny\Doctrine\Adapter\Nette\DI;
4
5
use Doctrine\Common\Annotations\AnnotationReader;
6
use Doctrine\Common\Annotations\AnnotationRegistry;
7
use Doctrine\Common\Annotations\CachedReader;
8
use Doctrine\Common\Annotations\Reader;
9
use Doctrine\Common\Cache\ApcuCache;
10
use Doctrine\Common\Cache\ArrayCache;
11
use Doctrine\Common\Cache\ChainCache;
12
use Doctrine\Common\Cache\RedisCache;
13
use Doctrine\Common\EventManager;
14
use Doctrine\Common\EventSubscriber;
15
use Doctrine\DBAL\Connection;
16
use Doctrine\DBAL\Tools\Console\Command\ImportCommand;
17
use Doctrine\ORM\Cache\CacheConfiguration;
18
use Doctrine\ORM\Cache\CacheFactory;
19
use Doctrine\ORM\Cache\DefaultCacheFactory;
20
use Doctrine\ORM\Cache\Logging\CacheLogger;
21
use Doctrine\ORM\Cache\Logging\CacheLoggerChain;
22
use Doctrine\ORM\Cache\Logging\StatisticsCacheLogger;
23
use Doctrine\ORM\Cache\RegionsConfiguration;
24
use Doctrine\ORM\Configuration;
25
use Doctrine\ORM\EntityManager;
26
use Doctrine\ORM\EntityManagerInterface;
27
use Doctrine\ORM\EntityRepository;
28
use Doctrine\ORM\Events;
29
use Doctrine\ORM\Mapping\Driver\AnnotationDriver;
30
use Doctrine\ORM\Mapping\UnderscoreNamingStrategy;
31
use Doctrine\ORM\Query\Filter\SQLFilter;
32
use Doctrine\ORM\Tools\Console\Command\ClearCache\MetadataCommand;
33
use Doctrine\ORM\Tools\Console\Command\ClearCache\QueryCommand;
34
use Doctrine\ORM\Tools\Console\Command\ClearCache\ResultCommand;
35
use Doctrine\ORM\Tools\Console\Command\ConvertMappingCommand;
36
use Doctrine\ORM\Tools\Console\Command\GenerateEntitiesCommand;
37
use Doctrine\ORM\Tools\Console\Command\GenerateProxiesCommand;
38
use Doctrine\ORM\Tools\Console\Command\SchemaTool\CreateCommand;
39
use Doctrine\ORM\Tools\Console\Command\SchemaTool\DropCommand;
40
use Doctrine\ORM\Tools\Console\Command\SchemaTool\UpdateCommand;
41
use Doctrine\ORM\Tools\Console\Command\ValidateSchemaCommand;
42
use Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper;
43
use Doctrine\ORM\Tools\ResolveTargetEntityListener;
44
use Nette\DI\CompilerExtension;
45
use Nette\DI\ContainerBuilder;
46
use Nette\DI\ServiceDefinition;
47
use Nette\DI\Statement;
48
use Nette\PhpGenerator\ClassType;
49
use Nette\PhpGenerator\PhpLiteral;
50
use Nette\Schema\Expect;
51
use Nette\Schema\Schema;
52
use Nette\Utils\AssertionException;
53
use Nette\Utils\Validators;
54
use Portiny\Doctrine\Adapter\Nette\Tracy\DoctrineSQLPanel;
55
use Portiny\Doctrine\Cache\DefaultCache;
56
use Portiny\Doctrine\Contract\Provider\ClassMappingProviderInterface;
57
use Portiny\Doctrine\Contract\Provider\EntitySourceProviderInterface;
58
use stdClass;
59
use Symfony\Component\Console\Application;
60
use Symfony\Component\Console\Helper\HelperSet;
61
62
class DoctrineExtension extends CompilerExtension
63
{
64
	private const DOCTRINE_SQL_PANEL = DoctrineSQLPanel::class;
65
66
	private $classMappings = [];
67
68
	private $entitySources = [];
69
70
71 1
	public function getConfigSchema(): Schema
72
	{
73 1
		return Expect::structure([
74 1
			'debug' => Expect::bool(false),
75 1
			'connection' => Expect::structure([
76 1
				'driver' => Expect::string('pdo_mysql'),
77 1
				'host' => Expect::string('localhost')->nullable(),
78 1
				'port' => Expect::int(3306)->nullable(),
79 1
				'user' => Expect::string('username')->nullable(),
80 1
				'password' => Expect::string('password')->nullable(),
81 1
				'dbname' => Expect::string('dbname')->nullable(),
82 1
				'memory' => Expect::bool(false)->nullable(),
83
			]),
84 1
			'dbal' => Expect::structure([
85 1
				'type_overrides' => Expect::array()->default([]),
86 1
				'types' => Expect::array()->default([]),
87 1
				'schema_filter' => Expect::string()->nullable(),
88
			]),
89 1
			'prefix' => Expect::string('doctrine.default'),
90 1
			'proxyDir' => Expect::string('%tempDir%/cache/proxies'),
91 1
			'proxyNamespace' => Expect::string('DoctrineProxies'),
92 1
			'sourceDir' => Expect::string()->nullable(),
93 1
			'entityManagerClassName' => Expect::string(EntityManager::class),
94 1
			'defaultRepositoryClassName' => Expect::string(EntityRepository::class),
95 1
			'repositoryFactory' => Expect::string()->nullable(),
96 1
			'namingStrategy' => Expect::string(UnderscoreNamingStrategy::class),
97 1
			'sqlLogger' => Expect::string()->nullable(),
98 1
			'targetEntityMappings' => Expect::array()->default([]),
99 1
			'metadata' => Expect::array()->default([]),
100 1
			'functions' => Expect::array()->default([]),
101
			// caches
102 1
			'metadataCache' => Expect::string('default'),
103 1
			'queryCache' => Expect::string('default'),
104 1
			'resultCache' => Expect::string('default'),
105 1
			'hydrationCache' => Expect::string('default'),
106 1
			'secondLevelCache' => Expect::structure([
107 1
				'enabled' => Expect::bool(false),
108 1
				'factoryClass' => Expect::string(DefaultCacheFactory::class),
109 1
				'driver' => Expect::string('default'),
110 1
				'regions' => Expect::structure([
111 1
					'defaultLifetime' => Expect::int(3600),
112 1
					'defaultLockLifetime' => Expect::int(60),
113
				]),
114 1
				'fileLockRegionDirectory' => Expect::string('%tempDir%/cache/Doctrine.Cache.Locks'),
115 1
				'logging' => Expect::bool(false),
116
			]),
117 1
			'cache' => Expect::structure([
118 1
				'redis' => Expect::structure([
119 1
					'class' => Expect::string(RedisCache::class),
120
				]),
121
			]),
122
		]);
123
	}
124
125
126
	/**
127
	 * {@inheritdoc}
128
	 */
129 1
	public function loadConfiguration(): void
130
	{
131 1
		$config = $this->parseConfig();
132
133 1
		$builder = $this->getContainerBuilder();
134 1
		$name = $config->prefix;
135
136 1
		$builder->addDefinition($name . '.namingStrategy')
137 1
			->setType($config->namingStrategy);
138
139 1
		$configurationDefinition = $builder->addDefinition($name . '.config')
140 1
			->setType(Configuration::class)
141 1
			->addSetup('setFilterSchemaAssetsExpression', [$config->dbal->schema_filter])
142 1
			->addSetup('setDefaultRepositoryClassName', [$config->defaultRepositoryClassName])
143 1
			->addSetup('setProxyDir', [$config->proxyDir])
144 1
			->addSetup('setProxyNamespace', [$config->proxyNamespace])
145 1
			->addSetup('setAutoGenerateProxyClasses', [$config->debug])
146 1
			->addSetup('setNamingStrategy', ['@' . $name . '.namingStrategy']);
147
148 1
		$builder->addDefinition($name . '.annotationReader')
149 1
			->setType(AnnotationReader::class)
150 1
			->setAutowired(false);
151
152 1
		$metadataCache = $this->getCache($name . '.metadata', $builder, $config->metadataCache ?: 'array');
153 1
		$builder->addDefinition($name . '.reader')
154 1
			->setType(Reader::class)
155 1
			->setFactory(CachedReader::class, ['@' . $name . '.annotationReader', $metadataCache, $config->debug]);
156
157 1
		$builder->addDefinition($name . '.annotationDriver')
158 1
			->setFactory(AnnotationDriver::class, ['@' . $name . '.reader', array_values($this->entitySources)]);
159
160 1
		$configurationDefinition->addSetup('setMetadataDriverImpl', ['@' . $name . '.annotationDriver']);
161
162 1
		foreach ($config->functions as $functionName => $function) {
163 1
			$configurationDefinition->addSetup('addCustomStringFunction', [$functionName, $function]);
164
		}
165
166 1
		if ($config->repositoryFactory) {
167
			$builder->addDefinition($name . '.repositoryFactory')
168
				->setType($config->repositoryFactory);
169
			$configurationDefinition->addSetup('setRepositoryFactory', ['@' . $name . '.repositoryFactory']);
170
		}
171 1
		if ($config->sqlLogger) {
172
			$builder->addDefinition($name . '.sqlLogger')
173
				->setType($config->sqlLogger);
174
			$configurationDefinition->addSetup('setSQLLogger', ['@' . $name . '.sqlLogger']);
175
		}
176
177 1
		if ($config->metadataCache !== false) {
178 1
			$configurationDefinition->addSetup(
179 1
				'setMetadataCacheImpl',
180 1
				[$this->getCache($name . '.metadata', $builder, $config->metadataCache)]
181
			);
182
		}
183
184 1
		if ($config->queryCache !== false) {
185 1
			$configurationDefinition->addSetup(
186 1
				'setQueryCacheImpl',
187 1
				[$this->getCache($name . '.query', $builder, $config->queryCache)]
188
			);
189
		}
190
191 1
		if ($config->resultCache !== false) {
192 1
			$configurationDefinition->addSetup(
193 1
				'setResultCacheImpl',
194 1
				[$this->getCache($name . '.ormResult', $builder, $config->resultCache)]
195
			);
196
		}
197
198 1
		if ($config->hydrationCache !== false) {
199 1
			$configurationDefinition->addSetup(
200 1
				'setHydrationCacheImpl',
201 1
				[$this->getCache($name . '.hydration', $builder, $config->hydrationCache)]
202
			);
203
		}
204
205 1
		$this->processSecondLevelCache($name, $config->secondLevelCache);
206
207 1
		$builder->addDefinition($name . '.connection')
208 1
			->setType(Connection::class)
209 1
			->setFactory('@' . $name . '.entityManager::getConnection');
210
211 1
		$builder->addDefinition($name . '.entityManager')
212 1
			->setType($config->entityManagerClassName)
213 1
			->setFactory(
214 1
				$config->entityManagerClassName . '::create',
215 1
				[(array) $config->connection, '@' . $name . '.config', '@Doctrine\Common\EventManager']
216
			);
217
218 1
		$builder->addDefinition($name . '.resolver')
219 1
			->setType(ResolveTargetEntityListener::class);
220
221 1
		if ($config->debug === true) {
222
			$builder->addDefinition($this->prefix($name . '.diagnosticsPanel'))
223
				->setType(self::DOCTRINE_SQL_PANEL);
224
		}
225
226
		// import Doctrine commands into Symfony/Console if exists
227 1
		$this->registerCommandsIntoConsole($builder, $name);
228 1
	}
229
230
231
	/**
232
	 * {@inheritdoc}
233
	 */
234 1
	public function beforeCompile(): void
235
	{
236
		/** @var stdClass $config */
237 1
		$config = (object) $this->config;
238 1
		$name = $config->prefix;
239
240 1
		$builder = $this->getContainerBuilder();
241
242 1
		foreach ($this->classMappings as $source => $target) {
243
			$builder->getDefinition($name . '.resolver')
244
				->addSetup('addResolveTargetEntity', [$source, $target, []]);
245
		}
246
247 1
		$this->processDbalTypes($name, $config->dbal->types);
248 1
		$this->processDbalTypeOverrides($name, $config->dbal->type_overrides);
249 1
		$this->processEventSubscribers($name);
250 1
		$this->processFilters();
251 1
	}
252
253
254
	/**
255
	 * {@inheritdoc}
256
	 */
257 1
	public function afterCompile(ClassType $classType): void
258
	{
259
		/** @var stdClass $config */
260 1
		$config = (object) $this->config;
261 1
		$initialize = $classType->methods['initialize'];
262
263 1
		$initialize->addBody('?::registerUniqueLoader("class_exists");', [new PhpLiteral(AnnotationRegistry::class)]);
264
265 1
		if ($config->debug === true) {
266
			$initialize->addBody('$this->getByType(\'' . self::DOCTRINE_SQL_PANEL . '\')->bindToBar();');
267
		}
268
269 1
		$builder = $this->getContainerBuilder();
270 1
		$filterDefinitions = $builder->findByType(SQLFilter::class);
271 1
		if ($filterDefinitions !== []) {
272
			$initialize->addBody(
273
				'$filterCollection = $this->getByType(\'' . EntityManagerInterface::class . '\')->getFilters();'
274
			);
275
			foreach (array_keys($filterDefinitions) as $name) {
276
				$initialize->addBody('$filterCollection->enable(\'' . $name . '\');');
277
			}
278
		}
279 1
	}
280
281
282 1
	protected function processSecondLevelCache($name, stdClass $config): void
283
	{
284 1
		if (! $config->enabled) {
285 1
			return;
286
		}
287
288
		$builder = $this->getContainerBuilder();
289
290
		$cacheService = $this->getCache($name . '.secondLevel', $builder, $config->driver);
291
292
		$cacheFactoryId = '@' . $name . '.cacheRegionsConfiguration';
293
		$builder->addDefinition($this->prefix($name . '.cacheFactory'))
294
			->setType(CacheFactory::class)
295
			->setFactory($config->factoryClass, [$this->prefix($cacheFactoryId), $cacheService])
296
			->addSetup('setFileLockRegionDirectory', [$config->fileLockRegionDirectory]);
297
298
		$builder->addDefinition($this->prefix($name . '.cacheRegionsConfiguration'))
299
			->setFactory(RegionsConfiguration::class, [
300
				$config->regions->defaultLifetime,
301
				$config->regions->defaultLockLifetime,
302
			]);
303
304
		$logger = $builder->addDefinition($this->prefix($name . '.cacheLogger'))
305
			->setType(CacheLogger::class)
306
			->setFactory(CacheLoggerChain::class)
307
			->setAutowired(false);
308
309
		if ($config->logging) {
310
			$logger->addSetup('setLogger', ['statistics', new Statement(StatisticsCacheLogger::class)]);
311
		}
312
313
		$cacheConfigName = $this->prefix($name . '.ormCacheConfiguration');
314
		$builder->addDefinition($cacheConfigName)
315
			->setType(CacheConfiguration::class)
316
			->addSetup('setCacheFactory', [$this->prefix('@' . $name . '.cacheFactory')])
317
			->addSetup('setCacheLogger', [$this->prefix('@' . $name . '.cacheLogger')])
318
			->setAutowired(false);
319
320
		$configuration = $builder->getDefinitionByType(Configuration::class);
321
		$configuration->addSetup('setSecondLevelCacheEnabled');
322
		$configuration->addSetup('setSecondLevelCacheConfiguration', ['@' . $cacheConfigName]);
323
	}
324
325
326
	/**
327
	 * @throws AssertionException
328
	 */
329 1
	private function parseConfig(): stdClass
330
	{
331
		/** @var stdClass $config */
332 1
		$config = (object) $this->config;
333
334 1
		$this->classMappings = $config->targetEntityMappings;
335 1
		$this->entitySources = $config->metadata;
336
337 1
		foreach ($this->compiler->getExtensions() as $extension) {
338 1
			if ($extension instanceof ClassMappingProviderInterface) {
339
				$entityMapping = $extension->getClassMapping();
340
				Validators::assert($entityMapping, 'array');
341
				$this->classMappings = array_merge($this->classMappings, $entityMapping);
342
			}
343
344 1
			if ($extension instanceof EntitySourceProviderInterface) {
345
				$entitySource = $extension->getEntitySource();
346
				Validators::assert($entitySource, 'array');
347
				$this->entitySources = array_merge($this->entitySources, $entitySource);
348
			}
349
		}
350
351 1
		if ($config->sourceDir) {
352
			$this->entitySources[] = $config->sourceDir;
353
		}
354
355 1
		return $config;
356
	}
357
358
359 1
	private function getCache(string $prefix, ContainerBuilder $containerBuilder, string $cacheType): string
360
	{
361 1
		if ($containerBuilder->hasDefinition($prefix . '.cache')) {
362 1
			return '@' . $prefix . '.cache';
363
		}
364
365 1
		$config = $this->parseConfig();
366
367 1
		switch ($cacheType) {
368 1
			case 'apcu':
369
				$cacheClass = ApcuCache::class;
370
				break;
371
372 1
			case 'array':
373
				$cacheClass = ArrayCache::class;
374
				break;
375
376 1
			case 'redis':
377
				$cacheClass = $config->cache->redis->class;
378
				break;
379
380 1
			case 'default':
381
			default:
382 1
				$cacheClass = DefaultCache::class;
383 1
				break;
384
		}
385
386 1
		$containerBuilder->addDefinition($prefix . '.cache1')
387 1
			->setType(ArrayCache::class)
388 1
			->setAutowired(false);
389
390 1
		$mainCacheDefinition = $containerBuilder->addDefinition($prefix . '.cache2')
391 1
			->setType($cacheClass)
392 1
			->setAutowired(false);
393
394 1
		$containerBuilder->addDefinition($prefix . '.cache')
395 1
			->setFactory(ChainCache::class, [['@' . $prefix . '.cache1', '@' . $prefix . '.cache2']])
396 1
			->setAutowired(false);
397
398 1
		if ($cacheType === 'redis') {
399
			$redisConfig = $config->cache->redis;
400
401
			$containerBuilder->addDefinition($prefix . '.redis')
402
				->setType('\Redis')
403
				->setAutowired(false)
404
				->addSetup('connect', [
405
					$redisConfig->host ?? '127.0.0.1',
406
					$redisConfig->port ?? null,
407
					$redisConfig->timeout ?? 0.0,
408
					$redisConfig->reserved ?? null,
409
					$redisConfig->retryInterval ?? 0,
410
				])
411
				->addSetup('select', [$redisConfig->database ?? 1]);
412
413
			$mainCacheDefinition->addSetup('setRedis', ['@' . $prefix . '.redis']);
414
		}
415
416 1
		return '@' . $prefix . '.cache';
417
	}
418
419
420 1
	private function registerCommandsIntoConsole(ContainerBuilder $containerBuilder, string $name): void
421
	{
422 1
		if ($this->hasSymfonyConsole()) {
423
			$commands = [
424 1
				ConvertMappingCommand::class,
425
				CreateCommand::class,
426
				DropCommand::class,
427
				GenerateEntitiesCommand::class,
428
				GenerateProxiesCommand::class,
429
				ImportCommand::class,
430
				MetadataCommand::class,
431
				QueryCommand::class,
432
				ResultCommand::class,
433
				UpdateCommand::class,
434
				ValidateSchemaCommand::class,
435
			];
436 1
			foreach ($commands as $index => $command) {
437 1
				$containerBuilder->addDefinition($name . '.command.' . $index)
438 1
					->setType($command);
439
			}
440
441 1
			$helperSets = $containerBuilder->findByType(HelperSet::class);
442 1
			if (! empty($helperSets)) {
443
				/** @var ServiceDefinition $helperSet */
444
				$helperSet = reset($helperSets);
445
				$helperSet->addSetup('set', [new Statement(EntityManagerHelper::class), 'em']);
446
			}
447
		}
448 1
	}
449
450
451 1
	private function processDbalTypes(string $name, array $types): void
452
	{
453 1
		$builder = $this->getContainerBuilder();
454 1
		$entityManagerDefinition = $builder->getDefinition($name . '.entityManager');
455
456 1
		foreach ($types as $type => $className) {
457 1
			$entityManagerDefinition->addSetup(
458 1
				'if ( ! Doctrine\DBAL\Types\Type::hasType(?)) { Doctrine\DBAL\Types\Type::addType(?, ?); }',
459 1
				[$type, $type, $className]
460
			);
461
		}
462 1
	}
463
464
465 1
	private function processDbalTypeOverrides(string $name, array $types): void
466
	{
467 1
		$builder = $this->getContainerBuilder();
468 1
		$entityManagerDefinition = $builder->getDefinition($name . '.entityManager');
469
470 1
		foreach ($types as $type => $className) {
471 1
			$entityManagerDefinition->addSetup('Doctrine\DBAL\Types\Type::overrideType(?, ?);', [$type, $className]);
472
		}
473 1
	}
474
475
476 1
	private function processEventSubscribers(string $name): void
477
	{
478 1
		$builder = $this->getContainerBuilder();
479
480 1
		if ($this->hasEventManager($builder)) {
481
			$eventManagerDefinition = $builder->getDefinition((string) $builder->getByType(EventManager::class))
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $eventManagerDefinition is correct as $builder->getDefinition(.... $name . '.resolver')) targeting Nette\DI\Definitions\Definition::__call() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
482
				->addSetup('addEventListener', [Events::loadClassMetadata, '@' . $name . '.resolver']);
483
		} else {
484 1
			$eventManagerDefinition = $builder->addDefinition($name . '.eventManager')
485 1
				->setType(EventManager::class)
486 1
				->addSetup('addEventListener', [Events::loadClassMetadata, '@' . $name . '.resolver']);
487
		}
488
489 1
		foreach (array_keys($builder->findByType(EventSubscriber::class)) as $serviceName) {
490 1
			$eventManagerDefinition->addSetup('addEventSubscriber', ['@' . $serviceName]);
491
		}
492 1
	}
493
494
495 1
	private function processFilters(): void
496
	{
497 1
		$builder = $this->getContainerBuilder();
498
499 1
		$configurationService = $builder->getDefinitionByType(Configuration::class);
500 1
		foreach ($builder->findByType(SQLFilter::class) as $name => $filterDefinition) {
501
			$configurationService->addSetup('addFilter', [$name, $filterDefinition->getType()]);
502
		}
503 1
	}
504
505
506 1
	private function hasSymfonyConsole(): bool
507
	{
508 1
		return class_exists(Application::class);
509
	}
510
511
512 1
	private function hasEventManager(ContainerBuilder $containerBuilder): bool
513
	{
514 1
		$eventManagerServiceName = $containerBuilder->getByType(EventManager::class);
515 1
		return $eventManagerServiceName !== null && strlen($eventManagerServiceName) > 0;
516
	}
517
518
}
519