Completed
Push — master ( 27b921...bfb6e5 )
by Tomáš
04:49
created

src/Adapter/Nette/DI/DoctrineExtension.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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\Cache\RedisCache;
10
use Doctrine\Common\EventManager;
11
use Doctrine\Common\EventSubscriber;
12
use Doctrine\DBAL\Connection;
13
use Doctrine\DBAL\Tools\Console\Command\ImportCommand;
14
use Doctrine\ORM\Cache\CacheConfiguration;
15
use Doctrine\ORM\Cache\CacheFactory;
16
use Doctrine\ORM\Cache\DefaultCacheFactory;
17
use Doctrine\ORM\Cache\Logging\CacheLogger;
18
use Doctrine\ORM\Cache\Logging\CacheLoggerChain;
19
use Doctrine\ORM\Cache\Logging\StatisticsCacheLogger;
20
use Doctrine\ORM\Cache\RegionsConfiguration;
21
use Doctrine\ORM\Configuration;
22
use Doctrine\ORM\EntityManager;
23
use Doctrine\ORM\EntityManagerInterface;
24
use Doctrine\ORM\EntityRepository;
25
use Doctrine\ORM\Events;
26
use Doctrine\ORM\Mapping\UnderscoreNamingStrategy;
27
use Doctrine\ORM\Query\Filter\SQLFilter;
28
use Doctrine\ORM\Tools\Console\Command\ClearCache\MetadataCommand;
29
use Doctrine\ORM\Tools\Console\Command\ClearCache\QueryCommand;
30
use Doctrine\ORM\Tools\Console\Command\ClearCache\ResultCommand;
31
use Doctrine\ORM\Tools\Console\Command\ConvertMappingCommand;
32
use Doctrine\ORM\Tools\Console\Command\GenerateEntitiesCommand;
33
use Doctrine\ORM\Tools\Console\Command\GenerateProxiesCommand;
34
use Doctrine\ORM\Tools\Console\Command\SchemaTool\CreateCommand;
35
use Doctrine\ORM\Tools\Console\Command\SchemaTool\DropCommand;
36
use Doctrine\ORM\Tools\Console\Command\SchemaTool\UpdateCommand;
37
use Doctrine\ORM\Tools\Console\Command\ValidateSchemaCommand;
38
use Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper;
39
use Doctrine\ORM\Tools\ResolveTargetEntityListener;
40
use Nette\DI\CompilerExtension;
41
use Nette\DI\ContainerBuilder;
42
use Nette\DI\ServiceDefinition;
43
use Nette\DI\Statement;
44
use Nette\PhpGenerator\ClassType;
45
use Nette\Utils\AssertionException;
46
use Nette\Utils\Validators;
47
use Portiny\Console\Adapter\Nette\DI\ConsoleExtension;
48
use Portiny\Doctrine\Adapter\Nette\Tracy\DoctrineSQLPanel;
49
use Portiny\Doctrine\Cache\DefaultCache;
50
use Portiny\Doctrine\Contract\Provider\ClassMappingProviderInterface;
51
use Portiny\Doctrine\Contract\Provider\EntitySourceProviderInterface;
52
use Symfony\Component\Console\Helper\HelperSet;
53
use Tracy\IBarPanel;
54
55
class DoctrineExtension extends CompilerExtension
56
{
57
	/**
58
	 * @var string
59
	 */
60
	private const DOCTRINE_SQL_PANEL = DoctrineSQLPanel::class;
61
62
	/**
63
	 * @var array
64
	 */
65
	private static $defaults = [
66
		'debug' => '%debugMode%',
67
		'dbal' => [
68
			'type_overrides' => [],
69
			'types' => [],
70
			'schema_filter' => NULL,
71
		],
72
		'prefix' => 'doctrine.default',
73
		'proxyDir' => '%tempDir%/cache/proxies',
74
		'sourceDir' => NULL,
75
		'entityManagerClassName' => EntityManager::class,
76
		'defaultRepositoryClassName' => EntityRepository::class,
77
		'repositoryFactory' => NULL,
78
		'namingStrategy' => UnderscoreNamingStrategy::class,
79
		'sqlLogger' => NULL,
80
		'targetEntityMappings' => [],
81
		'metadata' => [],
82
		'functions' => [],
83
		// caches
84
		'metadataCache' => 'default',
85
		'queryCache' => 'default',
86
		'resultCache' => 'default',
87
		'hydrationCache' => 'default',
88
		'secondLevelCache' => [
89
			'enabled' => FALSE,
90
			'factoryClass' => DefaultCacheFactory::class,
91
			'driver' => 'default',
92
			'regions' => [
93
				'defaultLifetime' => 3600,
94
				'defaultLockLifetime' => 60,
95
			],
96
			'fileLockRegionDirectory' => '%tempDir%/cache/Doctrine.Cache.Locks',
97
			'logging' => '%debugMode%',
98
		],
99
		'cache' => [
100
			'redis' => [],
101
		],
102
	];
103
104
	private $entitySources = [];
105
106
	private $classMappings = [];
107
108
	/**
109
	 * {@inheritdoc}
110
	 */
111 1
	public function loadConfiguration(): void
112
	{
113 1
		$config = $this->parseConfig();
114
115 1
		$builder = $this->getContainerBuilder();
116 1
		$name = $config['prefix'];
117
118 1
		$configurationDefinition = $builder->addDefinition($name . '.config')
119 1
			->setType(Configuration::class)
120 1
			->addSetup('setFilterSchemaAssetsExpression', [$config['dbal']['schema_filter']])
121 1
			->addSetup('setDefaultRepositoryClassName', [$config['defaultRepositoryClassName']]);
122
123 1
		if ($config['repositoryFactory']) {
124
			$builder->addDefinition($name . '.repositoryFactory')
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::setClass() has been deprecated.

This method has been deprecated.

Loading history...
125
				->setClass($config['repositoryFactory']);
126
			$configurationDefinition->addSetup('setRepositoryFactory', ['@' . $name . '.repositoryFactory']);
127
		}
128 1
		if ($config['sqlLogger']) {
129
			$builder->addDefinition($name . '.sqlLogger')
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::setClass() has been deprecated.

This method has been deprecated.

Loading history...
130
				->setClass($config['sqlLogger']);
131
			$configurationDefinition->addSetup('setSQLLogger', ['@' . $name . '.sqlLogger']);
132
		}
133
134 1
		if ($config['metadataCache'] !== FALSE) {
135 1
			$configurationDefinition->addSetup(
136 1
				'setMetadataCacheImpl',
137 1
				[$this->getCache($name . '.metadata', $builder, $config['metadataCache'])]
138
			);
139
		}
140
141 1
		if ($config['queryCache'] !== FALSE) {
142 1
			$configurationDefinition->addSetup(
143 1
				'setQueryCacheImpl',
144 1
				[$this->getCache($name . '.query', $builder, $config['queryCache'])]
145
			);
146
		}
147
148 1
		if ($config['resultCache'] !== FALSE) {
149 1
			$configurationDefinition->addSetup(
150 1
				'setResultCacheImpl',
151 1
				[$this->getCache($name . '.ormResult', $builder, $config['resultCache'])]
152
			);
153
		}
154
155 1
		if ($config['hydrationCache'] !== FALSE) {
156 1
			$configurationDefinition->addSetup(
157 1
				'setHydrationCacheImpl',
158 1
				[$this->getCache($name . '.hydration', $builder, $config['hydrationCache'])]
159
			);
160
		}
161
162 1
		$this->processSecondLevelCache($name, $config['secondLevelCache']);
163
164 1
		$builder->addDefinition($name . '.connection')
165 1
			->setType(Connection::class)
166 1
			->setFactory('@' . $name . '.entityManager::getConnection');
167
168 1
		$builder->addDefinition($name . '.entityManager')
169 1
			->setType($config['entityManagerClassName'])
170 1
			->setFactory(
171 1
				$config['entityManagerClassName'] . '::create',
172 1
				[$config['connection'], '@' . $name . '.config', '@Doctrine\Common\EventManager']
173
			);
174
175 1
		$builder->addDefinition($name . '.namingStrategy')
176 1
			->setType($config['namingStrategy']);
177
178 1
		$builder->addDefinition($name . '.resolver')
179 1
			->setType(ResolveTargetEntityListener::class);
180
181 1
		if ($this->hasIBarPanelInterface()) {
182 1
			$builder->addDefinition($this->prefix($name . '.diagnosticsPanel'))
183 1
				->setType(self::DOCTRINE_SQL_PANEL);
184
		}
185
186
		// import Doctrine commands into Portiny/Console if exists
187 1
		$this->registerCommandsIntoConsole($builder, $name);
188 1
	}
189
190
	/**
191
	 * {@inheritdoc}
192
	 */
193 1
	public function beforeCompile(): void
194
	{
195 1
		$config = $this->getConfig(self::$defaults);
196 1
		$name = $config['prefix'];
197
198 1
		$builder = $this->getContainerBuilder();
199
200 1
		$configDefinition = $builder->getDefinition($name . '.config')
201 1
			->setFactory(
202 1
				'\Doctrine\ORM\Tools\Setup::createAnnotationMetadataConfiguration',
203 1
				[array_values($this->entitySources), $config['debug'], $config['proxyDir'], NULL, FALSE]
204
			)
205 1
			->addSetup('setNamingStrategy', ['@' . $name . '.namingStrategy']);
206
207 1
		foreach ($config['functions'] as $functionName => $function) {
208 1
			$configDefinition->addSetup('addCustomStringFunction', [$functionName, $function]);
209
		}
210
211 1
		foreach ($this->classMappings as $source => $target) {
212
			$builder->getDefinition($name . '.resolver')
213
				->addSetup('addResolveTargetEntity', [$source, $target, []]);
214
		}
215
216 1
		$this->processDbalTypes($name, $config['dbal']['types']);
217 1
		$this->processDbalTypeOverrides($name, $config['dbal']['type_overrides']);
218 1
		$this->processEventSubscribers($name);
219 1
		$this->processFilters($name);
220 1
	}
221
222
	/**
223
	 * {@inheritdoc}
224
	 */
225 1
	public function afterCompile(ClassType $classType): void
226
	{
227 1
		$initialize = $classType->methods['initialize'];
228 1
		if ($this->hasIBarPanelInterface()) {
229 1
			$initialize->addBody('$this->getByType(\'' . self::DOCTRINE_SQL_PANEL . '\')->bindToBar();');
230
		}
231
232 1
		$initialize->addBody(
233 1
			'$filterCollection = $this->getByType(\'' . EntityManagerInterface::class . '\')->getFilters();'
234
		);
235 1
		$builder = $this->getContainerBuilder();
236 1
		foreach ($builder->findByType(SQLFilter::class) as $name => $filterDefinition) {
237
			$initialize->addBody('$filterCollection->enable(\'' . $name . '\');');
238
		}
239 1
	}
240
241 1
	protected function processSecondLevelCache($name, array $config): void
242
	{
243 1
		if (! $config['enabled']) {
244 1
			return;
245
		}
246
247
		$builder = $this->getContainerBuilder();
248
249
		$cacheService = $this->getCache($name . '.secondLevel', $builder, $config['driver']);
250
251
		$builder->addDefinition($this->prefix($name . '.cacheFactory'))
252
			->setType(CacheFactory::class)
253
			->setFactory($config['factoryClass'], [
254
				$this->prefix('@' . $name . '.cacheRegionsConfiguration'),
255
				$cacheService,
256
			])
257
			->addSetup('setFileLockRegionDirectory', [$config['fileLockRegionDirectory']]);
258
259
		$builder->addDefinition($this->prefix($name . '.cacheRegionsConfiguration'))
260
			->setFactory(RegionsConfiguration::class, [
261
				$config['regions']['defaultLifetime'],
262
				$config['regions']['defaultLockLifetime'],
263
			]);
264
265
		$logger = $builder->addDefinition($this->prefix($name . '.cacheLogger'))
266
			->setType(CacheLogger::class)
267
			->setFactory(CacheLoggerChain::class)
268
			->setAutowired(FALSE);
269
270
		if ($config['logging']) {
271
			$logger->addSetup('setLogger', ['statistics', new Statement(StatisticsCacheLogger::class)]);
272
		}
273
274
		$cacheConfigName = $this->prefix($name . '.ormCacheConfiguration');
275
		$builder->addDefinition($cacheConfigName)
276
			->setType(CacheConfiguration::class)
277
			->addSetup('setCacheFactory', [$this->prefix('@' . $name . '.cacheFactory')])
278
			->addSetup('setCacheLogger', [$this->prefix('@' . $name . '.cacheLogger')])
279
			->setAutowired(FALSE);
280
281
		$configuration = $builder->getDefinitionByType(Configuration::class);
282
		$configuration->addSetup('setSecondLevelCacheEnabled');
283
		$configuration->addSetup('setSecondLevelCacheConfiguration', ['@' . $cacheConfigName]);
284
	}
285
286 1
	private function getCache(string $prefix, ContainerBuilder $containerBuilder, string $cacheType): string
287
	{
288 1
		$cacheServiceName = $containerBuilder->getByType(Cache::class);
289 1
		if ($cacheServiceName !== NULL && strlen($cacheServiceName) > 0) {
290 1
			return '@' . $cacheServiceName;
291
		}
292
293 1
		$cacheClass = ArrayCache::class;
294 1
		if ($cacheType) {
295
			switch ($cacheType) {
296 1
				case 'redis':
297
					$cacheClass = RedisCache::class;
298
					break;
299
300 1
				case 'default':
301
				default:
302 1
					$cacheClass = DefaultCache::class;
303 1
					break;
304
			}
305
		}
306
307 1
		$cacheDefinition = $containerBuilder->addDefinition($prefix . '.cache')
308 1
			->setType($cacheClass);
309
310 1
		if ($cacheType === 'redis') {
311
			$config = $this->parseConfig();
312
			$redisConfig = $config['cache']['redis'];
313
314
			$containerBuilder->addDefinition($prefix . '.redis')
315
				->setType('\Redis')
316
				->setAutowired(FALSE)
317
				->addSetup('connect', [
318
					$redisConfig['host'] ?? '127.0.0.1',
319
					$redisConfig['port'] ?? null,
320
					$redisConfig['timeout'] ?? 0.0,
321
					$redisConfig['reserved'] ?? null,
322
					$redisConfig['retryInterval'] ?? 0,
323
				])
324
				->addSetup('select', [$redisConfig['database'] ?? 1]);
325
326
			$cacheDefinition->addSetup('setRedis', ['@' . $prefix . '.redis']);
327
		}
328
329 1
		return '@' . $prefix . '.cache';
330
	}
331
332
	/**
333
	 * @throws AssertionException
334
	 */
335 1
	private function parseConfig(): array
336
	{
337 1
		$config = $this->getConfig(self::$defaults);
338 1
		$this->classMappings = $config['targetEntityMappings'];
339 1
		$this->entitySources = $config['metadata'];
340
341 1
		foreach ($this->compiler->getExtensions() as $extension) {
342 1
			if ($extension instanceof ClassMappingProviderInterface) {
343
				$entityMapping = $extension->getClassMapping();
344
				Validators::assert($entityMapping, 'array');
345
				$this->classMappings = array_merge($this->classMappings, $entityMapping);
346
			}
347
348 1
			if ($extension instanceof EntitySourceProviderInterface) {
349
				$entitySource = $extension->getEntitySource();
350
				Validators::assert($entitySource, 'array');
351 1
				$this->entitySources = array_merge($this->entitySources, $entitySource);
352
			}
353
		}
354
355 1
		if ($config['sourceDir']) {
356
			$this->entitySources[] = $config['sourceDir'];
357
		}
358
359 1
		return $config;
360
	}
361
362 1
	private function hasIBarPanelInterface(): bool
363
	{
364 1
		return interface_exists(IBarPanel::class);
365
	}
366
367 1
	private function hasPortinyConsole(): bool
368
	{
369 1
		return class_exists(ConsoleExtension::class);
370
	}
371
372 1
	private function hasEventManager(ContainerBuilder $containerBuilder): bool
373
	{
374 1
		$eventManagerServiceName = $containerBuilder->getByType(EventManager::class);
375 1
		return $eventManagerServiceName !== NULL && strlen($eventManagerServiceName) > 0;
376
	}
377
378 1
	private function processDbalTypes(string $name, array $types): void
379
	{
380 1
		$builder = $this->getContainerBuilder();
381 1
		$entityManagerDefinition = $builder->getDefinition($name . '.entityManager');
382
383 1
		foreach ($types as $type => $className) {
384 1
			$entityManagerDefinition->addSetup(
385 1
				'if ( ! Doctrine\DBAL\Types\Type::hasType(?)) { Doctrine\DBAL\Types\Type::addType(?, ?); }',
386 1
				[$type, $type, $className]
387
			);
388
		}
389 1
	}
390
391 1
	private function processDbalTypeOverrides(string $name, array $types): void
392
	{
393 1
		$builder = $this->getContainerBuilder();
394 1
		$entityManagerDefinition = $builder->getDefinition($name . '.entityManager');
395
396 1
		foreach ($types as $type => $className) {
397 1
			$entityManagerDefinition->addSetup('Doctrine\DBAL\Types\Type::overrideType(?, ?);', [$type, $className]);
398
		}
399 1
	}
400
401 1
	private function processEventSubscribers(string $name): void
402
	{
403 1
		$builder = $this->getContainerBuilder();
404
405 1
		if ($this->hasEventManager($builder)) {
406
			$eventManagerDefinition = $builder->getDefinition($builder->getByType(EventManager::class))
407
				->addSetup('addEventListener', [Events::loadClassMetadata, '@' . $name . '.resolver']);
408
		} else {
409 1
			$eventManagerDefinition = $builder->addDefinition($name . '.eventManager')
410 1
				->setType(EventManager::class)
411 1
				->addSetup('addEventListener', [Events::loadClassMetadata, '@' . $name . '.resolver']);
412
		}
413
414 1
		foreach (array_keys($builder->findByType(EventSubscriber::class)) as $serviceName) {
415 1
			$eventManagerDefinition->addSetup('addEventSubscriber', ['@' . $serviceName]);
416
		}
417 1
	}
418
419 1
	private function processFilters(string $name): void
420
	{
421 1
		$builder = $this->getContainerBuilder();
422
423 1
		$configurationService = $builder->getDefinitionByType(Configuration::class);
424 1
		foreach ($builder->findByType(SQLFilter::class) as $name => $filterDefinition) {
425
			$configurationService->addSetup('addFilter', [$name, $filterDefinition->getType()]);
426
		}
427 1
	}
428
429 1
	private function registerCommandsIntoConsole(ContainerBuilder $containerBuilder, string $name): void
430
	{
431 1
		if ($this->hasPortinyConsole()) {
432
			$commands = [
433
				ConvertMappingCommand::class,
434
				CreateCommand::class,
435
				DropCommand::class,
436
				GenerateEntitiesCommand::class,
437
				GenerateProxiesCommand::class,
438
				ImportCommand::class,
439
				MetadataCommand::class,
440
				QueryCommand::class,
441
				ResultCommand::class,
442
				UpdateCommand::class,
443
				ValidateSchemaCommand::class,
444
			];
445
			foreach ($commands as $index => $command) {
446
				$containerBuilder->addDefinition($name . '.command.' . $index)
447
					->setType($command);
448
			}
449
450
			$helperSets = $containerBuilder->findByType(HelperSet::class);
451
			if ($helperSets) {
452
				/** @var ServiceDefinition $helperSet */
453
				$helperSet = reset($helperSets);
454
				$helperSet->addSetup('set', [new Statement(EntityManagerHelper::class), 'em']);
455
			}
456
		}
457 1
	}
458
}
459