Completed
Push — master ( c12550...96f7d2 )
by Tomáš
03:22
created

DoctrineExtension::getCache()   B

Complexity

Conditions 7
Paths 11

Size

Total Lines 58
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 11.4651

Importance

Changes 4
Bugs 0 Features 2
Metric Value
cc 7
eloc 40
c 4
b 0
f 2
nc 11
nop 3
dl 0
loc 58
ccs 22
cts 40
cp 0.55
crap 11.4651
rs 8.3466

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