Completed
Push — master ( bb7d96...d4201d )
by Tomáš
01:30
created

DoctrineExtension   A

Complexity

Total Complexity 29

Size/Duplication

Total Lines 246
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 83.62%

Importance

Changes 0
Metric Value
wmc 29
lcom 1
cbo 2
dl 0
loc 246
ccs 97
cts 116
cp 0.8362
rs 10
c 0
b 0
f 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
A processEventSubscribers() 0 17 3
B loadConfiguration() 0 64 5
B beforeCompile() 0 34 3
A afterCompile() 0 7 2
B parseConfig() 0 26 5
A getCache() 0 12 3
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
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\Configuration;
14
use Doctrine\ORM\EntityManager;
15
use Doctrine\ORM\Events;
16
use Doctrine\ORM\Mapping\UnderscoreNamingStrategy;
17
use Doctrine\ORM\Tools\Console\Command\ClearCache\MetadataCommand;
18
use Doctrine\ORM\Tools\Console\Command\ClearCache\QueryCommand;
19
use Doctrine\ORM\Tools\Console\Command\ClearCache\ResultCommand;
20
use Doctrine\ORM\Tools\Console\Command\ConvertMappingCommand;
21
use Doctrine\ORM\Tools\Console\Command\GenerateEntitiesCommand;
22
use Doctrine\ORM\Tools\Console\Command\GenerateProxiesCommand;
23
use Doctrine\ORM\Tools\Console\Command\SchemaTool\CreateCommand;
24
use Doctrine\ORM\Tools\Console\Command\SchemaTool\DropCommand;
25
use Doctrine\ORM\Tools\Console\Command\SchemaTool\UpdateCommand;
26
use Doctrine\ORM\Tools\Console\Command\ValidateSchemaCommand;
27
use Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper;
28
use Doctrine\ORM\Tools\ResolveTargetEntityListener;
29
use Nette\DI\CompilerExtension;
30
use Nette\DI\ContainerBuilder;
31
use Nette\DI\Statement;
32
use Nette\PhpGenerator\ClassType;
33
use Nette\Utils\AssertionException;
34
use Nette\Utils\Validators;
35
use Portiny\Console\Adapter\Nette\DI\ConsoleExtension;
36
use Portiny\Doctrine\Adapter\Nette\Tracy\DoctrineSQLPanel;
37
use Portiny\Doctrine\Contract\Provider\ClassMappingProviderInterface;
38
use Portiny\Doctrine\Contract\Provider\EntitySourceProviderInterface;
39
use Symfony\Component\Console\Helper\HelperSet;
40
use Tracy\IBarPanel;
41
42
class DoctrineExtension extends CompilerExtension
43
{
44
	/**
45
	 * @var string
46
	 */
47
	public const DOCTRINE_SQL_PANEL = DoctrineSQLPanel::class;
48
49
	/**
50
	 * @var array
51
	 */
52
	public static $defaults = [
53
		'debug' => TRUE,
54
		'dbal' => [
55
			'type_overrides' => [],
56
			'types' => [],
57
			'schema_filter' => NULL,
58
		],
59
		'prefix' => 'doctrine.default',
60
		'proxyDir' => '%tempDir%/cache/proxies',
61
		'sourceDir' => NULL,
62
		'targetEntityMappings' => [],
63
		'metadata' => [],
64
		'functions' => [],
65
	];
66
67
	private $entitySources = [];
68
69
	private $classMappings = [];
70
71
	/**
72
	 * {@inheritdoc}
73
	 */
74 1
	public function loadConfiguration(): void
75
	{
76 1
		$config = $this->parseConfig();
77
78 1
		$builder = $this->getContainerBuilder();
79 1
		$name = $config['prefix'];
80
81 1
		$builder->addDefinition($name . '.config')
82 1
			->setType(Configuration::class)
83 1
			->addSetup(new Statement('setFilterSchemaAssetsExpression', [$config['dbal']['schema_filter']]));
84
85 1
		$builder->addDefinition($name . '.connection')
86 1
			->setType(Connection::class)
87 1
			->setFactory('@' . $name . '.entityManager::getConnection');
88
89 1
		$builder->addDefinition($name . '.entityManager')
90 1
			->setType(EntityManager::class)
91 1
			->setFactory(
92 1
				'\Doctrine\ORM\EntityManager::create',
93
				[
94 1
					$config['connection'],
95 1
					'@' . $name . '.config',
96 1
					'@Doctrine\Common\EventManager',
97
				]
98
			);
99
100 1
		$builder->addDefinition($name . '.namingStrategy')
101 1
			->setType(UnderscoreNamingStrategy::class);
102
103 1
		$builder->addDefinition($name . '.resolver')
104 1
			->setType(ResolveTargetEntityListener::class);
105
106 1
		if ($this->hasIBarPanelInterface()) {
107 1
			$builder->addDefinition($this->prefix($name . '.diagnosticsPanel'))
108 1
				->setType(self::DOCTRINE_SQL_PANEL);
109
		}
110
111
		// import Doctrine commands into Portiny/Console
112 1
		if ($this->hasPortinyConsole()) {
113
			$commands = [
114
				ConvertMappingCommand::class,
115
				CreateCommand::class,
116
				DropCommand::class,
117
				GenerateEntitiesCommand::class,
118
				GenerateProxiesCommand::class,
119
				ImportCommand::class,
120
				MetadataCommand::class,
121
				QueryCommand::class,
122
				ResultCommand::class,
123
				UpdateCommand::class,
124
				ValidateSchemaCommand::class,
125
			];
126
			foreach ($commands as $index => $command) {
127
				$builder->addDefinition($name . '.command.' . $index)
128
					->setType($command);
129
			}
130
131
			$helperSets = $builder->findByType(HelperSet::class);
132
			if ($helperSets) {
133
				$helperSet = reset($helperSets);
134
				$helperSet->addSetup('set', [new Statement(EntityManagerHelper::class), 'em']);
135
			}
136
		}
137 1
	}
138
139
	/**
140
	 * {@inheritdoc}
141
	 */
142 1
	public function beforeCompile(): void
143
	{
144 1
		$config = $this->getConfig(self::$defaults);
145 1
		$name = $config['prefix'];
146
147 1
		$builder = $this->getContainerBuilder();
148 1
		$cache = $this->getCache($name, $builder);
149
150 1
		$configDefinition = $builder->getDefinition($name . '.config')
151 1
			->setFactory(
152 1
				'\Doctrine\ORM\Tools\Setup::createAnnotationMetadataConfiguration',
153
				[
154 1
					array_values($this->entitySources),
155 1
					$config['debug'],
156 1
					$config['proxyDir'],
157 1
					$cache,
158
					FALSE,
159
				]
160
			)
161 1
			->addSetup('setNamingStrategy', ['@' . $name . '.namingStrategy']);
162
163 1
		foreach ($config['functions'] as $functionName => $function) {
164 1
			$configDefinition->addSetup('addCustomStringFunction', [$functionName, $function]);
165
		}
166
167 1
		foreach ($this->classMappings as $source => $target) {
168
			$builder->getDefinition($name . '.resolver')
169
				->addSetup('addResolveTargetEntity', [$source, $target, []]);
170
		}
171
172 1
		$this->processDbalTypes($name, $config['dbal']['types']);
173 1
		$this->processDbalTypeOverrides($name, $config['dbal']['type_overrides']);
174 1
		$this->processEventSubscribers($name);
175 1
	}
176
177
	/**
178
	 * {@inheritdoc}
179
	 */
180 1
	public function afterCompile(ClassType $classType): void
181
	{
182 1
		$initialize = $classType->methods['initialize'];
183 1
		if ($this->hasIBarPanelInterface()) {
184 1
			$initialize->addBody('$this->getByType(\'' . self::DOCTRINE_SQL_PANEL . '\')->bindToBar();');
185
		}
186 1
	}
187
188
	/**
189
	 * @throws AssertionException
190
	 */
191 1
	private function parseConfig(): array
192
	{
193 1
		$config = $this->getConfig(self::$defaults);
194 1
		$this->classMappings = $config['targetEntityMappings'];
195 1
		$this->entitySources = $config['metadata'];
196
197 1
		foreach ($this->compiler->getExtensions() as $extension) {
198 1
			if ($extension instanceof ClassMappingProviderInterface) {
199
				$entityMapping = $extension->getClassMapping();
200
				Validators::assert($entityMapping, 'array');
201
				$this->classMappings = array_merge($this->classMappings, $entityMapping);
202
			}
203
204 1
			if ($extension instanceof EntitySourceProviderInterface) {
205
				$entitySource = $extension->getEntitySource();
206
				Validators::assert($entitySource, 'array');
207 1
				$this->entitySources = array_merge($this->entitySources, $entitySource);
208
			}
209
		}
210
211 1
		if ($config['sourceDir']) {
212
			$this->entitySources[] = $config['sourceDir'];
213
		}
214
215 1
		return $config;
216
	}
217
218 1
	private function getCache(string $prefix, ContainerBuilder $containerBuilder): string
219
	{
220 1
		$cacheServiceName = $containerBuilder->getByType(Cache::class);
221 1
		if ($cacheServiceName !== NULL && strlen($cacheServiceName) > 0) {
222
			return '@' . $cacheServiceName;
223
		}
224
225 1
		$containerBuilder->addDefinition($prefix . '.cache')
226 1
			->setType(ArrayCache::class);
227
228 1
		return '@' . $prefix . '.cache';
229
	}
230
231 1
	private function hasIBarPanelInterface(): bool
232
	{
233 1
		return interface_exists(IBarPanel::class);
234
	}
235
236 1
	private function hasPortinyConsole(): bool
237
	{
238 1
		return class_exists(ConsoleExtension::class);
239
	}
240
241 1
	private function hasEventManager(ContainerBuilder $containerBuilder): bool
242
	{
243 1
		$eventManagerServiceName = $containerBuilder->getByType(EventManager::class);
244 1
		return $eventManagerServiceName !== NULL && strlen($eventManagerServiceName) > 0;
245
	}
246
247 1
	private function processDbalTypes(string $name, array $types): void
248
	{
249 1
		$builder = $this->getContainerBuilder();
250 1
		$entityManagerDefinition = $builder->getDefinition($name . '.entityManager');
251
252 1
		foreach ($types as $type => $className) {
253 1
			$entityManagerDefinition->addSetup(
254 1
				'if ( ! Doctrine\DBAL\Types\Type::hasType(?)) { Doctrine\DBAL\Types\Type::addType(?, ?); }',
255 1
				[$type, $type, $className]
256
			);
257
		}
258 1
	}
259
260 1
	private function processDbalTypeOverrides(string $name, array $types): void
261
	{
262 1
		$builder = $this->getContainerBuilder();
263 1
		$entityManagerDefinition = $builder->getDefinition($name . '.entityManager');
264
265 1
		foreach ($types as $type => $className) {
266 1
			$entityManagerDefinition->addSetup('Doctrine\DBAL\Types\Type::overrideType(?, ?);', [$type, $className]);
267
		}
268 1
	}
269
270 1
	private function processEventSubscribers(string $name): void
271
	{
272 1
		$builder = $this->getContainerBuilder();
273
274 1
		if ($this->hasEventManager($builder)) {
275
			$eventManagerDefinition = $builder->getDefinition($builder->getByType(EventManager::class))
276
				->addSetup('addEventListener', [Events::loadClassMetadata, '@' . $name . '.resolver']);
277
		} else {
278 1
			$eventManagerDefinition = $builder->addDefinition($name . '.eventManager')
279 1
				->setType(EventManager::class)
280 1
				->addSetup('addEventListener', [Events::loadClassMetadata, '@' . $name . '.resolver']);
281
		}
282
283 1
		foreach (array_keys($builder->findByType(EventSubscriber::class)) as $serviceName) {
284 1
			$eventManagerDefinition->addSetup('addEventSubscriber', ['@' . $serviceName]);
285
		}
286 1
	}
287
}
288