Completed
Push — master ( d4201d...06cb54 )
by Tomáš
03:43
created

DoctrineExtension::hasPortinyConsole()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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