Completed
Push — master ( 09bdd0...0be494 )
by Tomáš
03:57
created

DoctrineExtension::afterCompile()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

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