1
|
|
|
<?php namespace Limoncello\Application\Data; |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* Copyright 2015-2017 [email protected] |
5
|
|
|
* |
6
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
7
|
|
|
* you may not use this file except in compliance with the License. |
8
|
|
|
* You may obtain a copy of the License at |
9
|
|
|
* |
10
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0 |
11
|
|
|
* |
12
|
|
|
* Unless required by applicable law or agreed to in writing, software |
13
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS, |
14
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
15
|
|
|
* See the License for the specific language governing permissions and |
16
|
|
|
* limitations under the License. |
17
|
|
|
*/ |
18
|
|
|
|
19
|
|
|
use DateTimeImmutable; |
20
|
|
|
use Doctrine\DBAL\Connection; |
21
|
|
|
use Doctrine\DBAL\Schema\AbstractSchemaManager; |
22
|
|
|
use Doctrine\DBAL\Schema\Table; |
23
|
|
|
use Doctrine\DBAL\Types\Type; |
24
|
|
|
use Generator; |
25
|
|
|
use Limoncello\Contracts\Commands\IoInterface; |
26
|
|
|
use Limoncello\Contracts\Data\MigrationInterface; |
27
|
|
|
use Psr\Container\ContainerInterface; |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* @package Limoncello\Application |
31
|
|
|
*/ |
32
|
|
|
abstract class BaseMigrationRunner |
33
|
|
|
{ |
34
|
|
|
/** Migrations table name */ |
35
|
|
|
const MIGRATIONS_TABLE = '_migrations'; |
36
|
|
|
|
37
|
|
|
/** Migration column name */ |
38
|
|
|
const MIGRATIONS_COLUMN_ID = 'id'; |
39
|
|
|
|
40
|
|
|
/** Migration column name */ |
41
|
|
|
const MIGRATIONS_COLUMN_CLASS = 'class'; |
42
|
|
|
|
43
|
|
|
/** Migration column name */ |
44
|
|
|
const MIGRATIONS_COLUMN_MIGRATED_AT = 'migrated_at'; |
45
|
|
|
|
46
|
|
|
/** Seeds table name */ |
47
|
|
|
const SEEDS_TABLE = '_seeds'; |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* @var IoInterface |
51
|
|
|
*/ |
52
|
|
|
private $inOut; |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* @return string[] |
56
|
|
|
*/ |
57
|
|
|
abstract protected function getMigrationClasses(): array; |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* @param IoInterface $inOut |
61
|
|
|
*/ |
62
|
2 |
|
public function __construct(IoInterface $inOut) |
63
|
|
|
{ |
64
|
2 |
|
$this->setIO($inOut); |
65
|
|
|
} |
66
|
|
|
|
67
|
|
|
/** |
68
|
|
|
* @param ContainerInterface $container |
69
|
|
|
* |
70
|
|
|
* @return void |
71
|
|
|
*/ |
72
|
1 |
|
public function migrate(ContainerInterface $container): void |
73
|
|
|
{ |
74
|
1 |
View Code Duplication |
foreach ($this->getMigrations($container) as $class) { |
|
|
|
|
75
|
1 |
|
assert(is_string($class)); |
76
|
1 |
|
$this->getIO()->writeInfo("Starting migration for `$class`..." . PHP_EOL, IoInterface::VERBOSITY_VERBOSE); |
|
|
|
|
77
|
|
|
/** @var MigrationInterface $migration */ |
78
|
1 |
|
$migration = new $class($container); |
79
|
1 |
|
$migration->init($container)->migrate(); |
80
|
1 |
|
$this->getIO()->writeInfo("Migration finished for `$class`." . PHP_EOL, IoInterface::VERBOSITY_NORMAL); |
|
|
|
|
81
|
|
|
} |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
/** |
85
|
|
|
* @param ContainerInterface $container |
86
|
|
|
* |
87
|
|
|
* @return void |
88
|
|
|
*/ |
89
|
1 |
|
public function rollback(ContainerInterface $container): void |
90
|
|
|
{ |
91
|
1 |
View Code Duplication |
foreach ($this->getRollbacks($container) as $class) { |
|
|
|
|
92
|
1 |
|
assert(is_string($class)); |
93
|
1 |
|
$this->getIO()->writeInfo("Starting rollback for `$class`..." . PHP_EOL, IoInterface::VERBOSITY_VERBOSE); |
|
|
|
|
94
|
|
|
/** @var MigrationInterface $migration */ |
95
|
1 |
|
$migration = new $class($container); |
96
|
1 |
|
$migration->init($container)->rollback(); |
97
|
1 |
|
$this->getIO()->writeInfo("Rollback finished for `$class`." . PHP_EOL, IoInterface::VERBOSITY_NORMAL); |
|
|
|
|
98
|
|
|
} |
99
|
|
|
|
100
|
1 |
|
$manager = $this->getConnection($container)->getSchemaManager(); |
101
|
1 |
|
if ($manager->tablesExist([static::MIGRATIONS_TABLE]) === true) { |
102
|
1 |
|
$manager->dropTable(static::MIGRATIONS_TABLE); |
103
|
|
|
} |
104
|
1 |
|
if ($manager->tablesExist([static::SEEDS_TABLE]) === true) { |
105
|
1 |
|
$manager->dropTable(static::SEEDS_TABLE); |
106
|
|
|
} |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
/** |
110
|
|
|
* @return IoInterface |
111
|
|
|
*/ |
112
|
1 |
|
protected function getIO(): IoInterface |
113
|
|
|
{ |
114
|
1 |
|
return $this->inOut; |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
/** |
118
|
|
|
* @param IoInterface $inOut |
119
|
|
|
* |
120
|
|
|
* @return self |
121
|
|
|
*/ |
122
|
2 |
|
private function setIO(IoInterface $inOut): self |
123
|
|
|
{ |
124
|
2 |
|
$this->inOut = $inOut; |
125
|
|
|
|
126
|
2 |
|
return $this; |
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
/** |
130
|
|
|
* @param ContainerInterface $container |
131
|
|
|
* |
132
|
|
|
* @return Generator |
133
|
|
|
* |
134
|
|
|
* @SuppressWarnings(PHPMD.ElseExpression) |
135
|
|
|
*/ |
136
|
1 |
View Code Duplication |
private function getMigrations(ContainerInterface $container): Generator |
|
|
|
|
137
|
|
|
{ |
138
|
1 |
|
$connection = $this->getConnection($container); |
139
|
1 |
|
$manager = $connection->getSchemaManager(); |
140
|
|
|
|
141
|
1 |
|
if ($manager->tablesExist([static::MIGRATIONS_TABLE]) === true) { |
142
|
1 |
|
$migrated = $this->readMigrated($connection); |
143
|
|
|
} else { |
144
|
1 |
|
$this->createMigrationsTable($manager); |
145
|
1 |
|
$migrated = []; |
146
|
|
|
} |
147
|
|
|
|
148
|
1 |
|
$notYetMigrated = array_diff($this->getMigrationClasses(), $migrated); |
149
|
|
|
|
150
|
1 |
|
foreach ($notYetMigrated as $class) { |
151
|
1 |
|
yield $class; |
152
|
1 |
|
$this->saveMigration($connection, $class); |
153
|
|
|
} |
154
|
|
|
} |
155
|
|
|
|
156
|
|
|
/** |
157
|
|
|
* @param ContainerInterface $container |
158
|
|
|
* |
159
|
|
|
* @return Generator |
160
|
|
|
*/ |
161
|
1 |
|
private function getRollbacks(ContainerInterface $container): Generator |
162
|
|
|
{ |
163
|
1 |
|
$connection = $this->getConnection($container); |
164
|
1 |
|
$migrated = $this->readMigrated($connection); |
165
|
|
|
|
166
|
1 |
|
foreach (array_reverse($migrated, true) as $index => $class) { |
167
|
1 |
|
yield $class; |
168
|
1 |
|
$this->removeMigration($connection, $index); |
169
|
|
|
} |
170
|
|
|
} |
171
|
|
|
|
172
|
|
|
/** |
173
|
|
|
* @param ContainerInterface $container |
174
|
|
|
* |
175
|
|
|
* @return Connection |
176
|
|
|
*/ |
177
|
1 |
|
private function getConnection(ContainerInterface $container): Connection |
178
|
|
|
{ |
179
|
1 |
|
return $container->get(Connection::class); |
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
/** |
183
|
|
|
* @param AbstractSchemaManager $manager |
184
|
|
|
* |
185
|
|
|
* @return void |
186
|
|
|
*/ |
187
|
1 |
View Code Duplication |
private function createMigrationsTable(AbstractSchemaManager $manager): void |
|
|
|
|
188
|
|
|
{ |
189
|
1 |
|
$table = new Table(static::MIGRATIONS_TABLE); |
190
|
|
|
|
191
|
|
|
$table |
192
|
1 |
|
->addColumn(static::MIGRATIONS_COLUMN_ID, Type::INTEGER) |
193
|
1 |
|
->setUnsigned(true) |
194
|
1 |
|
->setAutoincrement(true); |
195
|
|
|
$table |
196
|
1 |
|
->addColumn(static::MIGRATIONS_COLUMN_CLASS, Type::STRING) |
197
|
1 |
|
->setLength(255); |
198
|
|
|
$table |
199
|
1 |
|
->addColumn(static::MIGRATIONS_COLUMN_MIGRATED_AT, Type::DATETIME); |
200
|
|
|
|
201
|
1 |
|
$table->setPrimaryKey([static::MIGRATIONS_COLUMN_ID]); |
202
|
1 |
|
$table->addUniqueIndex([static::MIGRATIONS_COLUMN_CLASS]); |
203
|
|
|
|
204
|
1 |
|
$manager->createTable($table); |
205
|
|
|
} |
206
|
|
|
|
207
|
|
|
/** |
208
|
|
|
* @param Connection $connection |
209
|
|
|
* |
210
|
|
|
* @return array |
211
|
|
|
*/ |
212
|
1 |
|
private function readMigrated(Connection $connection): array |
213
|
|
|
{ |
214
|
1 |
|
$builder = $connection->createQueryBuilder(); |
215
|
1 |
|
$migrated = []; |
216
|
|
|
|
217
|
1 |
|
if ($connection->getSchemaManager()->tablesExist([static::MIGRATIONS_TABLE]) === true) { |
218
|
|
|
$migrations = $builder |
219
|
1 |
|
->select(static::MIGRATIONS_COLUMN_ID, static::MIGRATIONS_COLUMN_CLASS) |
220
|
1 |
|
->from(static::MIGRATIONS_TABLE) |
221
|
1 |
|
->orderBy(static::MIGRATIONS_COLUMN_ID) |
222
|
1 |
|
->execute() |
223
|
1 |
|
->fetchAll(); |
224
|
1 |
|
foreach ($migrations as $migration) { |
225
|
1 |
|
$index = $migration[static::MIGRATIONS_COLUMN_ID]; |
226
|
1 |
|
$class = $migration[static::MIGRATIONS_COLUMN_CLASS]; |
227
|
1 |
|
$migrated[$index] = $class; |
228
|
|
|
} |
229
|
|
|
} |
230
|
|
|
|
231
|
1 |
|
return $migrated; |
232
|
|
|
} |
233
|
|
|
|
234
|
|
|
/** |
235
|
|
|
* @param Connection $connection |
236
|
|
|
* @param string $class |
237
|
|
|
* |
238
|
|
|
* @return void |
239
|
|
|
*/ |
240
|
1 |
View Code Duplication |
private function saveMigration(Connection $connection, string $class): void |
|
|
|
|
241
|
|
|
{ |
242
|
1 |
|
$format = $connection->getSchemaManager()->getDatabasePlatform()->getDateTimeFormatString(); |
243
|
1 |
|
$now = (new DateTimeImmutable())->format($format); |
244
|
1 |
|
$connection->insert(static::MIGRATIONS_TABLE, [ |
245
|
1 |
|
static::MIGRATIONS_COLUMN_CLASS => $class, |
246
|
1 |
|
static::MIGRATIONS_COLUMN_MIGRATED_AT => $now, |
247
|
|
|
]); |
248
|
|
|
} |
249
|
|
|
|
250
|
|
|
/** |
251
|
|
|
* @param Connection $connection |
252
|
|
|
* @param int $index |
253
|
|
|
* |
254
|
|
|
* @return void |
255
|
|
|
*/ |
256
|
1 |
|
private function removeMigration(Connection $connection, int $index): void |
257
|
|
|
{ |
258
|
1 |
|
$connection->delete(static::MIGRATIONS_TABLE, [static::MIGRATIONS_COLUMN_ID => $index]); |
259
|
|
|
} |
260
|
|
|
} |
261
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.