Passed
Push — master ( 1c3f46...f90b5e )
by Tomáš
05:55
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 0
Metric Value
cc 10
eloc 64
nc 256
nop 0
dl 0
loc 99
ccs 57
cts 65
cp 0.8769
crap 10.1865
rs 5.6921
c 0
b 0
f 0

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
2
3
declare(strict_types=1);
4
5
namespace Portiny\Doctrine\Adapter\Nette\DI;
6
7
use Doctrine\Common\Annotations\AnnotationReader;
8
use Doctrine\Common\Annotations\AnnotationRegistry;
9
use Doctrine\Common\Annotations\CachedReader;
10
use Doctrine\Common\Annotations\Reader;
11
use Doctrine\Common\Cache\ApcuCache;
12
use Doctrine\Common\Cache\ArrayCache;
13
use Doctrine\Common\Cache\ChainCache;
14
use Doctrine\Common\Cache\RedisCache;
15
use Doctrine\Common\EventManager;
16
use Doctrine\Common\EventSubscriber;
17
use Doctrine\DBAL\Connection;
18
use Doctrine\DBAL\Tools\Console\Command\ImportCommand;
19
use Doctrine\ORM\Cache\CacheConfiguration;
20
use Doctrine\ORM\Cache\CacheFactory;
21
use Doctrine\ORM\Cache\DefaultCacheFactory;
22
use Doctrine\ORM\Cache\Logging\CacheLogger;
23
use Doctrine\ORM\Cache\Logging\CacheLoggerChain;
24
use Doctrine\ORM\Cache\Logging\StatisticsCacheLogger;
25
use Doctrine\ORM\Cache\RegionsConfiguration;
26
use Doctrine\ORM\Configuration;
27
use Doctrine\ORM\EntityManager;
28
use Doctrine\ORM\EntityManagerInterface;
29
use Doctrine\ORM\EntityRepository;
30
use Doctrine\ORM\Events;
31
use Doctrine\ORM\Mapping\Driver\AnnotationDriver;
32
use Doctrine\ORM\Mapping\UnderscoreNamingStrategy;
33
use Doctrine\ORM\Query\Filter\SQLFilter;
34
use Doctrine\ORM\Tools\Console\Command\ClearCache\MetadataCommand;
35
use Doctrine\ORM\Tools\Console\Command\ClearCache\QueryCommand;
36
use Doctrine\ORM\Tools\Console\Command\ClearCache\ResultCommand;
37
use Doctrine\ORM\Tools\Console\Command\ConvertMappingCommand;
38
use Doctrine\ORM\Tools\Console\Command\GenerateEntitiesCommand;
39
use Doctrine\ORM\Tools\Console\Command\GenerateProxiesCommand;
40
use Doctrine\ORM\Tools\Console\Command\SchemaTool\CreateCommand;
41
use Doctrine\ORM\Tools\Console\Command\SchemaTool\DropCommand;
42
use Doctrine\ORM\Tools\Console\Command\SchemaTool\UpdateCommand;
43
use Doctrine\ORM\Tools\Console\Command\ValidateSchemaCommand;
44
use Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper;
45
use Doctrine\ORM\Tools\ResolveTargetEntityListener;
46
use Nette\DI\CompilerExtension;
47
use Nette\DI\ContainerBuilder;
48
use Nette\DI\ServiceDefinition;
49
use Nette\DI\Statement;
50
use Nette\PhpGenerator\ClassType;
51
use Nette\PhpGenerator\PhpLiteral;
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 Symfony\Component\Console\Application;
59
use Symfony\Component\Console\Helper\HelperSet;
60
61
class DoctrineExtension extends CompilerExtension
62
{
63
	/**
64
	 * @var string
65
	 */
66
	private const DOCTRINE_SQL_PANEL = DoctrineSQLPanel::class;
67
68
	private $classMappings = [];
69
70
	private $entitySources = [];
71
72
	/**
73
	 * @var array
74
	 */
75
	private static $defaults = [
76
		'debug' => '%debugMode%',
77
		'dbal' => [
78
			'type_overrides' => [],
79
			'types' => [],
80
			'schema_filter' => null,
81
		],
82
		'prefix' => 'doctrine.default',
83
		'proxyDir' => '%tempDir%/cache/proxies',
84
		'proxyNamespace' => 'DoctrineProxies',
85
		'sourceDir' => null,
86
		'entityManagerClassName' => EntityManager::class,
87
		'defaultRepositoryClassName' => EntityRepository::class,
88
		'repositoryFactory' => null,
89
		'namingStrategy' => UnderscoreNamingStrategy::class,
90
		'sqlLogger' => null,
91
		'targetEntityMappings' => [],
92
		'metadata' => [],
93
		'functions' => [],
94
		// caches
95
		'metadataCache' => 'default',
96
		'queryCache' => 'default',
97
		'resultCache' => 'default',
98
		'hydrationCache' => 'default',
99
		'secondLevelCache' => [
100
			'enabled' => false,
101
			'factoryClass' => DefaultCacheFactory::class,
102
			'driver' => 'default',
103
			'regions' => [
104
				'defaultLifetime' => 3600,
105
				'defaultLockLifetime' => 60,
106
			],
107
			'fileLockRegionDirectory' => '%tempDir%/cache/Doctrine.Cache.Locks',
108
			'logging' => '%debugMode%',
109
		],
110
		'cache' => [
111
			'redis' => [
112
				'class' => RedisCache::class,
113
			],
114
		],
115
	];
116
117
	/**
118 1
	 * {@inheritdoc}
119
	 */
120 1
	public function loadConfiguration(): void
121
	{
122 1
		$config = $this->parseConfig();
123 1
124
		$builder = $this->getContainerBuilder();
125 1
		$name = $config['prefix'];
126 1
127
		$builder->addDefinition($name . '.namingStrategy')
128 1
			->setType($config['namingStrategy']);
129 1
130 1
		$configurationDefinition = $builder->addDefinition($name . '.config')
131 1
			->setType(Configuration::class)
132 1
			->addSetup('setFilterSchemaAssetsExpression', [$config['dbal']['schema_filter']])
133 1
			->addSetup('setDefaultRepositoryClassName', [$config['defaultRepositoryClassName']])
134 1
			->addSetup('setProxyDir', [$config['proxyDir']])
135 1
			->addSetup('setProxyNamespace', [$config['proxyNamespace']])
136
			->addSetup('setAutoGenerateProxyClasses', [$config['debug']])
137 1
			->addSetup('setNamingStrategy', ['@' . $name . '.namingStrategy']);
138 1
139 1
		$builder->addDefinition($name . '.annotationReader')
140
			->setType(AnnotationReader::class)
141 1
			->setAutowired(false);
142 1
143 1
		$metadataCache = $this->getCache($name . '.metadata', $builder, $config['metadataCache'] ?: 'array');
144 1
		$builder->addDefinition($name . '.reader')
145
			->setType(Reader::class)
146 1
			->setFactory(CachedReader::class, ['@' . $name . '.annotationReader', $metadataCache, $config['debug']]);
147 1
148
		$builder->addDefinition($name . '.annotationDriver')
149 1
			->setFactory(AnnotationDriver::class, ['@' . $name . '.reader', array_values($this->entitySources)]);
150
151 1
		$configurationDefinition->addSetup('setMetadataDriverImpl', ['@' . $name . '.annotationDriver']);
152 1
153
		foreach ($config['functions'] as $functionName => $function) {
154
			$configurationDefinition->addSetup('addCustomStringFunction', [$functionName, $function]);
155 1
		}
156
157
		if ($config['repositoryFactory']) {
158
			$builder->addDefinition($name . '.repositoryFactory')
159
				->setType($config['repositoryFactory']);
160 1
			$configurationDefinition->addSetup('setRepositoryFactory', ['@' . $name . '.repositoryFactory']);
161
		}
162
		if ($config['sqlLogger']) {
163
			$builder->addDefinition($name . '.sqlLogger')
164
				->setType($config['sqlLogger']);
165
			$configurationDefinition->addSetup('setSQLLogger', ['@' . $name . '.sqlLogger']);
166 1
		}
167 1
168 1
		if ($config['metadataCache'] !== false) {
169 1
			$configurationDefinition->addSetup(
170
				'setMetadataCacheImpl',
171
				[$this->getCache($name . '.metadata', $builder, $config['metadataCache'])]
172
			);
173 1
		}
174 1
175 1
		if ($config['queryCache'] !== false) {
176 1
			$configurationDefinition->addSetup(
177
				'setQueryCacheImpl',
178
				[$this->getCache($name . '.query', $builder, $config['queryCache'])]
179
			);
180 1
		}
181 1
182 1
		if ($config['resultCache'] !== false) {
183 1
			$configurationDefinition->addSetup(
184
				'setResultCacheImpl',
185
				[$this->getCache($name . '.ormResult', $builder, $config['resultCache'])]
186
			);
187 1
		}
188 1
189 1
		if ($config['hydrationCache'] !== false) {
190 1
			$configurationDefinition->addSetup(
191
				'setHydrationCacheImpl',
192
				[$this->getCache($name . '.hydration', $builder, $config['hydrationCache'])]
193
			);
194 1
		}
195
196 1
		$this->processSecondLevelCache($name, $config['secondLevelCache']);
197 1
198 1
		$builder->addDefinition($name . '.connection')
199
			->setType(Connection::class)
200 1
			->setFactory('@' . $name . '.entityManager::getConnection');
201 1
202 1
		$builder->addDefinition($name . '.entityManager')
203 1
			->setType($config['entityManagerClassName'])
204 1
			->setFactory(
205
				$config['entityManagerClassName'] . '::create',
206
				[$config['connection'], '@' . $name . '.config', '@Doctrine\Common\EventManager']
207 1
			);
208 1
209
		$builder->addDefinition($name . '.resolver')
210 1
			->setType(ResolveTargetEntityListener::class);
211
212
		if ($config['debug'] === true) {
213
			$builder->addDefinition($this->prefix($name . '.diagnosticsPanel'))
214
				->setType(self::DOCTRINE_SQL_PANEL);
215
		}
216 1
217 1
		// import Doctrine commands into Symfony/Console if exists
218
		$this->registerCommandsIntoConsole($builder, $name);
219
	}
220
221
	/**
222 1
	 * {@inheritdoc}
223
	 */
224 1
	public function beforeCompile(): void
225 1
	{
226
		$config = $this->getConfig(self::$defaults);
227 1
		$name = $config['prefix'];
228
229 1
		$builder = $this->getContainerBuilder();
230
231
		foreach ($this->classMappings as $source => $target) {
232
			$builder->getDefinition($name . '.resolver')
233
				->addSetup('addResolveTargetEntity', [$source, $target, []]);
234 1
		}
235 1
236 1
		$this->processDbalTypes($name, $config['dbal']['types']);
237 1
		$this->processDbalTypeOverrides($name, $config['dbal']['type_overrides']);
238 1
		$this->processEventSubscribers($name);
239
		$this->processFilters();
240
	}
241
242
	/**
243 1
	 * {@inheritdoc}
244
	 */
245 1
	public function afterCompile(ClassType $classType): void
246 1
	{
247
		$config = $this->getConfig(self::$defaults);
248 1
		$initialize = $classType->methods['initialize'];
249
250 1
		$initialize->addBody('?::registerUniqueLoader("class_exists");', [new PhpLiteral(AnnotationRegistry::class)]);
251
252
		if ($config['debug'] === true) {
253
			$initialize->addBody('$this->getByType(\'' . self::DOCTRINE_SQL_PANEL . '\')->bindToBar();');
254 1
		}
255 1
256 1
		$builder = $this->getContainerBuilder();
257
		$filterDefinitions = $builder->findByType(SQLFilter::class);
258
		if ($filterDefinitions !== []) {
259
			$initialize->addBody(
260
				'$filterCollection = $this->getByType(\'' . EntityManagerInterface::class . '\')->getFilters();'
261
			);
262
			foreach (array_keys($filterDefinitions) as $name) {
263
				$initialize->addBody('$filterCollection->enable(\'' . $name . '\');');
264 1
			}
265
		}
266 1
	}
267
268 1
	protected function processSecondLevelCache($name, array $config): void
269 1
	{
270
		if (! $config['enabled']) {
271
			return;
272
		}
273
274
		$builder = $this->getContainerBuilder();
275
276
		$cacheService = $this->getCache($name . '.secondLevel', $builder, $config['driver']);
277
278
		$builder->addDefinition($this->prefix($name . '.cacheFactory'))
279
			->setType(CacheFactory::class)
280
			->setFactory($config['factoryClass'], [
281
				$this->prefix('@' . $name . '.cacheRegionsConfiguration'),
282
				$cacheService,
283
			])
284
			->addSetup('setFileLockRegionDirectory', [$config['fileLockRegionDirectory']]);
285
286
		$builder->addDefinition($this->prefix($name . '.cacheRegionsConfiguration'))
287
			->setFactory(RegionsConfiguration::class, [
288
				$config['regions']['defaultLifetime'],
289
				$config['regions']['defaultLockLifetime'],
290
			]);
291
292
		$logger = $builder->addDefinition($this->prefix($name . '.cacheLogger'))
293
			->setType(CacheLogger::class)
294
			->setFactory(CacheLoggerChain::class)
295
			->setAutowired(false);
296
297
		if ($config['logging']) {
298
			$logger->addSetup('setLogger', ['statistics', new Statement(StatisticsCacheLogger::class)]);
299
		}
300
301
		$cacheConfigName = $this->prefix($name . '.ormCacheConfiguration');
302
		$builder->addDefinition($cacheConfigName)
303
			->setType(CacheConfiguration::class)
304
			->addSetup('setCacheFactory', [$this->prefix('@' . $name . '.cacheFactory')])
305
			->addSetup('setCacheLogger', [$this->prefix('@' . $name . '.cacheLogger')])
306
			->setAutowired(false);
307
308
		$configuration = $builder->getDefinitionByType(Configuration::class);
309
		$configuration->addSetup('setSecondLevelCacheEnabled');
310
		$configuration->addSetup('setSecondLevelCacheConfiguration', ['@' . $cacheConfigName]);
311
	}
312
313
	/**
314 1
	 * @throws AssertionException
315
	 */
316 1
	private function parseConfig(): array
317 1
	{
318 1
		$config = $this->getConfig(self::$defaults);
319
		$this->classMappings = $config['targetEntityMappings'];
320 1
		$this->entitySources = $config['metadata'];
321 1
322
		foreach ($this->compiler->getExtensions() as $extension) {
323
			if ($extension instanceof ClassMappingProviderInterface) {
324
				$entityMapping = $extension->getClassMapping();
325
				Validators::assert($entityMapping, 'array');
326
				$this->classMappings = array_merge($this->classMappings, $entityMapping);
0 ignored issues
show
Bug introduced by
It seems like $entityMapping can also be of type Nette\DI\CompilerExtensi...appingProviderInterface; however, parameter $array2 of array_merge() does only seem to accept array|null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

326
				$this->classMappings = array_merge($this->classMappings, /** @scrutinizer ignore-type */ $entityMapping);
Loading history...
327 1
			}
328
329
			if ($extension instanceof EntitySourceProviderInterface) {
330 1
				$entitySource = $extension->getEntitySource();
331
				Validators::assert($entitySource, 'array');
332
				$this->entitySources = array_merge($this->entitySources, $entitySource);
0 ignored issues
show
Bug introduced by
It seems like $entitySource can also be of type Nette\DI\CompilerExtensi...SourceProviderInterface; however, parameter $array2 of array_merge() does only seem to accept array|null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

332
				$this->entitySources = array_merge($this->entitySources, /** @scrutinizer ignore-type */ $entitySource);
Loading history...
333
			}
334 1
		}
335
336
		if ($config['sourceDir']) {
337
			$this->entitySources[] = $config['sourceDir'];
338 1
		}
339
340
		return $config;
341 1
	}
342
343 1
	private function getCache(string $prefix, ContainerBuilder $containerBuilder, string $cacheType): string
344 1
	{
345
		if ($containerBuilder->hasDefinition($prefix . '.cache')) {
346
			return '@' . $prefix . '.cache';
347 1
		}
348
349
		$config = $this->parseConfig();
350 1
351
		switch ($cacheType) {
352
			case 'apcu':
353
				$cacheClass = ApcuCache::class;
354 1
				break;
355
356
			case 'array':
357
				$cacheClass = ArrayCache::class;
358 1
				break;
359
360 1
			case 'redis':
361 1
				$cacheClass = $config['cache']['redis']['class'];
362
				break;
363
364 1
			case 'default':
365 1
			default:
366
				$cacheClass = DefaultCache::class;
367 1
				break;
368
		}
369
370
		$containerBuilder->addDefinition($prefix . '.cache1')
371
			->setType(ArrayCache::class)
372
			->setAutowired(false);
373
374
		$mainCacheDefinition = $containerBuilder->addDefinition($prefix . '.cache2')
375
			->setType($cacheClass)
376
			->setAutowired(false);
377
378
		$containerBuilder->addDefinition($prefix . '.cache')
379
			->setFactory(ChainCache::class, [['@' . $prefix . '.cache1', '@' . $prefix . '.cache2']])
380
			->setAutowired(false);
381
382
		if ($cacheType === 'redis') {
383
			$redisConfig = $config['cache']['redis'];
384
385 1
			$containerBuilder->addDefinition($prefix . '.redis')
386
				->setType('\Redis')
387
				->setAutowired(false)
388 1
				->addSetup('connect', [
389
					$redisConfig['host'] ?? '127.0.0.1',
390 1
					$redisConfig['port'] ?? null,
391
					$redisConfig['timeout'] ?? 0.0,
392 1
					$redisConfig['reserved'] ?? null,
393
					$redisConfig['retryInterval'] ?? 0,
394
				])
395
				->addSetup('select', [$redisConfig['database'] ?? 1]);
396
397
			$mainCacheDefinition->addSetup('setRedis', ['@' . $prefix . '.redis']);
398
		}
399
400
		return '@' . $prefix . '.cache';
401
	}
402
403
	private function registerCommandsIntoConsole(ContainerBuilder $containerBuilder, string $name): void
404 1
	{
405 1
		if ($this->hasSymfonyConsole()) {
406 1
			$commands = [
407
				ConvertMappingCommand::class,
408
				CreateCommand::class,
409 1
				DropCommand::class,
410 1
				GenerateEntitiesCommand::class,
411
				GenerateProxiesCommand::class,
412
				ImportCommand::class,
413
				MetadataCommand::class,
414
				QueryCommand::class,
415
				ResultCommand::class,
416 1
				UpdateCommand::class,
417
				ValidateSchemaCommand::class,
418 1
			];
419
			foreach ($commands as $index => $command) {
420 1
				$containerBuilder->addDefinition($name . '.command.' . $index)
421 1
					->setType($command);
422
			}
423 1
424 1
			$helperSets = $containerBuilder->findByType(HelperSet::class);
425 1
			if (! empty($helperSets)) {
426 1
				/** @var ServiceDefinition $helperSet */
427
				$helperSet = reset($helperSets);
428
				$helperSet->addSetup('set', [new Statement(EntityManagerHelper::class), 'em']);
429 1
			}
430
		}
431 1
	}
432
433 1
	private function processDbalTypes(string $name, array $types): void
434 1
	{
435
		$builder = $this->getContainerBuilder();
436 1
		$entityManagerDefinition = $builder->getDefinition($name . '.entityManager');
437 1
438
		foreach ($types as $type => $className) {
439 1
			$entityManagerDefinition->addSetup(
440
				'if ( ! Doctrine\DBAL\Types\Type::hasType(?)) { Doctrine\DBAL\Types\Type::addType(?, ?); }',
441 1
				[$type, $type, $className]
442
			);
443 1
		}
444
	}
445 1
446
	private function processDbalTypeOverrides(string $name, array $types): void
447
	{
448
		$builder = $this->getContainerBuilder();
449 1
		$entityManagerDefinition = $builder->getDefinition($name . '.entityManager');
450 1
451 1
		foreach ($types as $type => $className) {
452
			$entityManagerDefinition->addSetup('Doctrine\DBAL\Types\Type::overrideType(?, ?);', [$type, $className]);
453
		}
454 1
	}
455 1
456
	private function processEventSubscribers(string $name): void
457 1
	{
458
		$builder = $this->getContainerBuilder();
459 1
460
		if ($this->hasEventManager($builder)) {
461 1
			$eventManagerDefinition = $builder->getDefinition($builder->getByType(EventManager::class))
462
				->addSetup('addEventListener', [Events::loadClassMetadata, '@' . $name . '.resolver']);
463 1
		} else {
464 1
			$eventManagerDefinition = $builder->addDefinition($name . '.eventManager')
465
				->setType(EventManager::class)
466
				->addSetup('addEventListener', [Events::loadClassMetadata, '@' . $name . '.resolver']);
467 1
		}
468
469 1
		foreach (array_keys($builder->findByType(EventSubscriber::class)) as $serviceName) {
470
			$eventManagerDefinition->addSetup('addEventSubscriber', ['@' . $serviceName]);
471 1
		}
472
	}
473
474 1
	private function processFilters(): void
475
	{
476 1
		$builder = $this->getContainerBuilder();
477 1
478
		$configurationService = $builder->getDefinitionByType(Configuration::class);
479
		foreach ($builder->findByType(SQLFilter::class) as $name => $filterDefinition) {
480
			$configurationService->addSetup('addFilter', [$name, $filterDefinition->getType()]);
481
		}
482
	}
483
484
	private function hasSymfonyConsole(): bool
485
	{
486
		return class_exists(Application::class);
487
	}
488
489
	private function hasEventManager(ContainerBuilder $containerBuilder): bool
490
	{
491
		$eventManagerServiceName = $containerBuilder->getByType(EventManager::class);
492
		return $eventManagerServiceName !== null && strlen($eventManagerServiceName) > 0;
493
	}
494
}
495