Completed
Push — master ( 3052c6...61dd2a )
by Tomáš
06:55
created

DoctrineExtension::loadConfiguration()   B

Complexity

Conditions 8
Paths 128

Size

Total Lines 78

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 44
CRAP Score 8.1105

Importance

Changes 0
Metric Value
dl 0
loc 78
ccs 44
cts 50
cp 0.88
rs 7.0488
c 0
b 0
f 0
cc 8
nc 128
nop 0
crap 8.1105

How to fix   Long Method   

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