1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
namespace Liip\MonitorBundle\DependencyInjection\DoctrineMigrations; |
6
|
|
|
|
7
|
|
|
use Doctrine\DBAL\Connection; |
8
|
|
|
use Doctrine\DBAL\Driver\PDO\SQLite\Driver; |
9
|
|
|
use Doctrine\DBAL\Driver\PDOSqlite\Driver as LegacyDriver; |
10
|
|
|
use Doctrine\Migrations\Configuration\AbstractFileConfiguration; |
11
|
|
|
use Doctrine\Migrations\Configuration\Configuration as DoctrineMigrationConfiguration; |
12
|
|
|
use Liip\MonitorBundle\DoctrineMigrations\Configuration; |
13
|
|
|
use Symfony\Component\DependencyInjection\ChildDefinition; |
14
|
|
|
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; |
15
|
|
|
use Symfony\Component\DependencyInjection\ContainerBuilder; |
16
|
|
|
use Symfony\Component\DependencyInjection\Definition; |
17
|
|
|
use Symfony\Component\DependencyInjection\Reference; |
18
|
|
|
|
19
|
|
|
/** |
20
|
|
|
* Class V2MigrationsLoader. |
21
|
|
|
*/ |
22
|
|
|
final class V2MigrationsLoader extends AbstractDoctrineMigrationsLoader implements CompilerPassInterface |
23
|
|
|
{ |
24
|
|
|
/** |
25
|
|
|
* Connection object needed for correct migration loading. |
26
|
|
|
* |
27
|
|
|
* @var Connection |
28
|
|
|
*/ |
29
|
|
|
private $fakeConnection; |
30
|
|
|
|
31
|
|
|
/** |
32
|
|
|
* Tuple (migrationsConfiguration, tempConfiguration) for doctrine migrations check. |
33
|
|
|
* |
34
|
|
|
* @var array |
35
|
|
|
*/ |
36
|
|
|
private $migrationConfigurationsServices = []; |
37
|
|
|
|
38
|
|
|
public function process(ContainerBuilder $container) |
39
|
|
|
{ |
40
|
|
|
foreach ($this->migrationConfigurationsServices as $services) { |
41
|
|
|
[$configurationService, $configuration] = $services; |
|
|
|
|
42
|
|
|
/** @var Definition $configurationService */ |
43
|
|
|
/** @var DoctrineMigrationConfiguration $configuration */ |
44
|
|
|
$versions = $this->getPredefinedMigrations($container, $configuration, $this->fakeConnection); |
45
|
|
|
if ($versions) { |
|
|
|
|
46
|
|
|
$configurationService->addMethodCall('registerMigrations', [$versions]); |
47
|
|
|
} |
48
|
|
|
} |
49
|
|
|
} |
50
|
|
|
|
51
|
|
|
public function createMigrationConfigurationService( |
52
|
|
|
ContainerBuilder $container, |
53
|
|
|
string $connectionName, |
54
|
|
|
string $serviceId, |
55
|
|
|
string $filename = null |
56
|
|
|
): void { |
57
|
|
|
if (!$container->has(Configuration::class)) { |
58
|
|
|
$container->register(Configuration::class) |
59
|
|
|
->setAbstract(true) |
60
|
|
|
->setPublic(true) |
61
|
|
|
->setArguments([null]) |
62
|
|
|
->addMethodCall('setContainer', [new Reference('service_container')]); |
63
|
|
|
} |
64
|
|
|
|
65
|
|
|
$configuration = $this->createTemporaryConfiguration($container, $this->getConnection(), $filename); |
66
|
|
|
|
67
|
|
|
$serviceConfiguration = new ChildDefinition(Configuration::class); |
68
|
|
|
|
69
|
|
|
$this->migrationConfigurationsServices[] = [$serviceConfiguration, $configuration]; |
70
|
|
|
|
71
|
|
|
$serviceConfiguration->replaceArgument( |
72
|
|
|
0, |
73
|
|
|
new Reference(sprintf('doctrine.dbal.%s_connection', $connectionName)) |
74
|
|
|
); |
75
|
|
|
|
76
|
|
|
if ($configuration->getMigrationsNamespace()) { |
|
|
|
|
77
|
|
|
$serviceConfiguration->addMethodCall( |
78
|
|
|
'setMigrationsNamespace', |
79
|
|
|
[$configuration->getMigrationsNamespace()] |
|
|
|
|
80
|
|
|
); |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
if ($configuration->getMigrationsTableName()) { |
|
|
|
|
84
|
|
|
$serviceConfiguration->addMethodCall( |
85
|
|
|
'setMigrationsTableName', |
86
|
|
|
[$configuration->getMigrationsTableName()] |
|
|
|
|
87
|
|
|
); |
88
|
|
|
} |
89
|
|
|
|
90
|
|
|
if ($configuration->getMigrationsColumnName()) { |
|
|
|
|
91
|
|
|
$serviceConfiguration->addMethodCall( |
92
|
|
|
'setMigrationsColumnName', |
93
|
|
|
[$configuration->getMigrationsColumnName()] |
|
|
|
|
94
|
|
|
); |
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
if ($configuration->getName()) { |
|
|
|
|
98
|
|
|
$serviceConfiguration->addMethodCall('setName', [$configuration->getName()]); |
|
|
|
|
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
if ($configuration->getMigrationsDirectory()) { |
|
|
|
|
102
|
|
|
$directory = $configuration->getMigrationsDirectory(); |
|
|
|
|
103
|
|
|
$pathPlaceholders = ['kernel.root_dir', 'kernel.cache_dir', 'kernel.logs_dir']; |
104
|
|
|
foreach ($pathPlaceholders as $parameter) { |
105
|
|
|
$kernelDir = realpath($container->getParameter($parameter)); |
106
|
|
|
if (0 === strpos(realpath($directory), $kernelDir)) { |
107
|
|
|
$directory = str_replace($kernelDir, "%{$parameter}%", $directory); |
108
|
|
|
break; |
109
|
|
|
} |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
$serviceConfiguration->addMethodCall( |
113
|
|
|
'setMigrationsDirectory', |
114
|
|
|
[$directory] |
115
|
|
|
); |
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
$serviceConfiguration->addMethodCall('configure', []); |
119
|
|
|
|
120
|
|
|
if ($configuration->areMigrationsOrganizedByYear()) { |
121
|
|
|
$serviceConfiguration->addMethodCall('setMigrationsAreOrganizedByYear', [true]); |
122
|
|
|
} elseif ($configuration->areMigrationsOrganizedByYearAndMonth()) { |
123
|
|
|
$serviceConfiguration->addMethodCall('setMigrationsAreOrganizedByYearAndMonth', [true]); |
124
|
|
|
} |
125
|
|
|
|
126
|
|
|
$container->setDefinition($serviceId, $serviceConfiguration); |
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
/** |
130
|
|
|
* Creates in-memory migration configuration for setting up container service. |
131
|
|
|
* |
132
|
|
|
* @param ContainerBuilder $container The container |
133
|
|
|
* @param Connection $connection Fake connection |
134
|
|
|
* @param string $filename Migrations configuration file |
135
|
|
|
*/ |
136
|
|
|
private function createTemporaryConfiguration( |
137
|
|
|
ContainerBuilder $container, |
138
|
|
|
Connection $connection, |
139
|
|
|
string $filename = null |
140
|
|
|
): DoctrineMigrationConfiguration { |
141
|
|
|
if (null === $filename) { |
142
|
|
|
// this is configured from migrations bundle |
143
|
|
|
return new DoctrineMigrationConfiguration($connection); |
|
|
|
|
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
// ------- |
147
|
|
|
// This part must be in sync with Doctrine\Migrations\Tools\Console\Helper\ConfigurationHelper::loadConfig |
148
|
|
|
$map = [ |
149
|
|
|
'xml' => '\XmlConfiguration', |
150
|
|
|
'yaml' => '\YamlConfiguration', |
151
|
|
|
'yml' => '\YamlConfiguration', |
152
|
|
|
'php' => '\ArrayConfiguration', |
153
|
|
|
'json' => '\JsonConfiguration', |
154
|
|
|
]; |
155
|
|
|
// -------- |
156
|
|
|
|
157
|
|
|
$filename = $container->getParameterBag()->resolveValue($filename); |
158
|
|
|
$info = pathinfo($filename); |
159
|
|
|
// check we can support this file type |
160
|
|
|
if (empty($map[$info['extension']])) { |
161
|
|
|
throw new \InvalidArgumentException('Given config file type is not supported'); |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
$class = 'Doctrine\Migrations\Configuration'; |
165
|
|
|
$class .= $map[$info['extension']]; |
166
|
|
|
// ------- |
167
|
|
|
|
168
|
|
|
/** @var AbstractFileConfiguration $configuration */ |
169
|
|
|
$configuration = new $class($connection); |
170
|
|
|
$configuration->load($filename); |
171
|
|
|
$configuration->validate(); |
172
|
|
|
|
173
|
|
|
return $configuration; |
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
private function getConnection(): Connection |
177
|
|
|
{ |
178
|
|
|
if (null === $this->fakeConnection) { |
179
|
|
|
if (!class_exists(Connection::class)) { |
180
|
|
|
throw new \InvalidArgumentException(sprintf('Can not configure doctrine migration checks, because of absence of "%s" class', Connection::class)); |
181
|
|
|
} |
182
|
|
|
|
183
|
|
|
$driver = class_exists(Driver::class) |
184
|
|
|
? new Driver() |
185
|
|
|
: new LegacyDriver(); |
|
|
|
|
186
|
|
|
|
187
|
|
|
$this->fakeConnection = new Connection([], $driver); |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
return $this->fakeConnection; |
191
|
|
|
} |
192
|
|
|
|
193
|
|
|
/** |
194
|
|
|
* Return key-value array with migration version as key and class as a value defined in config file. |
195
|
|
|
* |
196
|
|
|
* @param ContainerBuilder $container The container |
197
|
|
|
* @param DoctrineMigrationConfiguration $config Current configuration |
198
|
|
|
* @param Connection $connection Fake connections |
199
|
|
|
* |
200
|
|
|
* @return string[] |
201
|
|
|
*/ |
202
|
|
|
private function getPredefinedMigrations(ContainerBuilder $container, DoctrineMigrationConfiguration $config, Connection $connection): array |
203
|
|
|
{ |
204
|
|
|
$result = []; |
205
|
|
|
|
206
|
|
|
$diff = new Configuration($connection); |
|
|
|
|
207
|
|
|
|
208
|
|
|
if ($namespace = $config->getMigrationsNamespace()) { |
|
|
|
|
209
|
|
|
$diff->setMigrationsNamespace($config->getMigrationsNamespace()); |
|
|
|
|
210
|
|
|
} |
211
|
|
|
|
212
|
|
|
if ($dir = $config->getMigrationsDirectory()) { |
|
|
|
|
213
|
|
|
$diff->setMigrationsDirectory($dir); |
|
|
|
|
214
|
|
|
} |
215
|
|
|
|
216
|
|
|
$diff->setContainer($container); |
217
|
|
|
$diff->configure(); |
218
|
|
|
|
219
|
|
|
foreach ($config->getMigrations() as $version) { |
|
|
|
|
220
|
|
|
$result[$version->getVersion()] = get_class($version->getMigration()); |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
foreach ($diff->getAvailableVersions() as $version) { |
|
|
|
|
224
|
|
|
unset($result[$version]); |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
return $result; |
228
|
|
|
} |
229
|
|
|
} |
230
|
|
|
|
This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.