Completed
Push — master ( 730c63...1997d4 )
by Tomáš
09:23
created

DoctrineExtension   B

Complexity

Total Complexity 41

Size/Duplication

Total Lines 356
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 10

Test Coverage

Coverage 72.72%

Importance

Changes 0
Metric Value
wmc 41
lcom 1
cbo 10
dl 0
loc 356
ccs 128
cts 176
cp 0.7272
rs 8.2769
c 0
b 0
f 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
B loadConfiguration() 0 63 6
B beforeCompile() 0 56 6
A afterCompile() 0 15 3
B processSecondLevelCache() 0 44 3
B getCache() 0 22 5
B parseConfig() 0 26 5
A hasIBarPanelInterface() 0 4 1
A hasPortinyConsole() 0 4 1
A hasEventManager() 0 5 2
A processDbalTypes() 0 12 2
A processDbalTypeOverrides() 0 9 2
A processEventSubscribers() 0 17 3
A processFilters() 0 9 2

How to fix   Complexity   

Complex Class

Complex classes like DoctrineExtension often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use DoctrineExtension, and based on these observations, apply Extract Interface, too.

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

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
399
	{
400 1
		$builder = $this->getContainerBuilder();
401
402 1
		$configurationService = $builder->getDefinitionByType(Configuration::class);
403 1
		foreach ($builder->findByType(SQLFilter::class) as $name => $filterDefinition) {
404
			$configurationService->addSetup('addFilter', [$name, $filterDefinition->getType()]);
405
		}
406 1
	}
407
}
408