1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Doctrine\DBAL\Migrations\Configuration; |
4
|
|
|
|
5
|
|
|
use Doctrine\Common\EventArgs; |
6
|
|
|
use Doctrine\DBAL\Connection; |
7
|
|
|
use Doctrine\DBAL\Connections\MasterSlaveConnection; |
8
|
|
|
use Doctrine\DBAL\Migrations\FileQueryWriter; |
9
|
|
|
use Doctrine\DBAL\Migrations\Finder\MigrationDeepFinderInterface; |
10
|
|
|
use Doctrine\DBAL\Migrations\MigrationException; |
11
|
|
|
use Doctrine\DBAL\Migrations\OutputWriter; |
12
|
|
|
use Doctrine\DBAL\Migrations\QueryWriter; |
13
|
|
|
use Doctrine\DBAL\Migrations\Version; |
14
|
|
|
use Doctrine\DBAL\Migrations\Finder\MigrationFinderInterface; |
15
|
|
|
use Doctrine\DBAL\Migrations\Finder\RecursiveRegexFinder; |
16
|
|
|
use Doctrine\DBAL\Schema\Column; |
17
|
|
|
use Doctrine\DBAL\Schema\Table; |
18
|
|
|
use Doctrine\DBAL\Types\Type; |
19
|
|
|
|
20
|
|
|
/** |
21
|
|
|
* Default Migration Configuration object used for configuring an instance of |
22
|
|
|
* the Migration class. Set the connection, version table name, register migration |
23
|
|
|
* classes/versions, etc. |
24
|
|
|
* |
25
|
|
|
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL |
26
|
|
|
* @link www.doctrine-project.org |
27
|
|
|
* @since 2.0 |
28
|
|
|
* @author Jonathan H. Wage <[email protected]> |
29
|
|
|
*/ |
30
|
|
|
class Configuration |
31
|
|
|
{ |
32
|
|
|
/** |
33
|
|
|
* Configure versions to be organized by year. |
34
|
|
|
*/ |
35
|
|
|
const VERSIONS_ORGANIZATION_BY_YEAR = 'year'; |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* Configure versions to be organized by year and month. |
39
|
|
|
* |
40
|
|
|
* @var string |
41
|
|
|
*/ |
42
|
|
|
const VERSIONS_ORGANIZATION_BY_YEAR_AND_MONTH = 'year_and_month'; |
43
|
|
|
|
44
|
|
|
/** |
45
|
|
|
* The date format for new version numbers |
46
|
|
|
*/ |
47
|
|
|
const VERSION_FORMAT = 'YmdHis'; |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* Name of this set of migrations |
51
|
|
|
* |
52
|
|
|
* @var string |
53
|
|
|
*/ |
54
|
|
|
private $name; |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* Flag for whether or not the migration table has been created |
58
|
|
|
* |
59
|
|
|
* @var boolean |
60
|
|
|
*/ |
61
|
|
|
private $migrationTableCreated = false; |
62
|
|
|
|
63
|
|
|
/** |
64
|
|
|
* Connection instance to use for migrations |
65
|
|
|
* |
66
|
|
|
* @var Connection |
67
|
|
|
*/ |
68
|
|
|
private $connection; |
69
|
|
|
|
70
|
|
|
/** |
71
|
|
|
* OutputWriter instance for writing output during migrations |
72
|
|
|
* |
73
|
|
|
* @var OutputWriter |
74
|
|
|
*/ |
75
|
|
|
private $outputWriter; |
76
|
|
|
|
77
|
|
|
/** |
78
|
|
|
* The migration finder implementation -- used to load migrations from a |
79
|
|
|
* directory. |
80
|
|
|
* |
81
|
|
|
* @var MigrationFinderInterface |
82
|
|
|
*/ |
83
|
|
|
private $migrationFinder; |
84
|
|
|
|
85
|
|
|
/** |
86
|
|
|
* @var QueryWriter |
87
|
|
|
*/ |
88
|
|
|
private $queryWriter; |
89
|
|
|
|
90
|
|
|
/** |
91
|
|
|
* The migration table name to track versions in |
92
|
|
|
* |
93
|
|
|
* @var string |
94
|
|
|
*/ |
95
|
|
|
private $migrationsTableName = 'doctrine_migration_versions'; |
96
|
|
|
|
97
|
|
|
/** |
98
|
|
|
* The migration column name to track versions in |
99
|
|
|
* |
100
|
|
|
* @var string |
101
|
|
|
*/ |
102
|
|
|
private $migrationsColumnName = 'version'; |
103
|
|
|
|
104
|
|
|
/** |
105
|
|
|
* The path to a directory where new migration classes will be written |
106
|
|
|
* |
107
|
|
|
* @var string |
108
|
|
|
*/ |
109
|
|
|
private $migrationsDirectory; |
110
|
|
|
|
111
|
|
|
/** |
112
|
|
|
* Namespace the migration classes live in |
113
|
|
|
* |
114
|
|
|
* @var string |
115
|
|
|
*/ |
116
|
|
|
private $migrationsNamespace; |
117
|
|
|
|
118
|
|
|
/** |
119
|
|
|
* Array of the registered migrations |
120
|
|
|
* |
121
|
|
|
* @var Version[] |
122
|
|
|
*/ |
123
|
|
|
private $migrations = []; |
124
|
|
|
|
125
|
|
|
/** |
126
|
|
|
* Versions are organized by year. |
127
|
|
|
* |
128
|
|
|
* @var boolean |
129
|
|
|
*/ |
130
|
|
|
private $migrationsAreOrganizedByYear = false; |
131
|
|
|
|
132
|
|
|
/** |
133
|
|
|
* Versions are organized by year and month. |
134
|
|
|
* |
135
|
|
|
* @var boolean |
136
|
|
|
*/ |
137
|
|
|
private $migrationsAreOrganizedByYearAndMonth = false; |
138
|
|
|
|
139
|
|
|
/** |
140
|
|
|
* The custom template path to be used in generate command |
141
|
|
|
* |
142
|
|
|
* @var string |
143
|
|
|
*/ |
144
|
|
|
private $customTemplate; |
145
|
|
|
|
146
|
|
|
/** |
147
|
|
|
* Prevent write queries. |
148
|
|
|
* |
149
|
|
|
* @var bool |
150
|
|
|
*/ |
151
|
|
|
private $isDryRun = false; |
152
|
|
|
|
153
|
|
|
/** |
154
|
|
|
* Construct a migration configuration object. |
155
|
|
|
* |
156
|
|
|
* @param Connection $connection A Connection instance |
157
|
|
|
* @param OutputWriter $outputWriter A OutputWriter instance |
158
|
|
|
* @param MigrationFinderInterface $finder Migration files finder |
159
|
|
|
* @param QueryWriter|null $queryWriter |
160
|
|
|
*/ |
161
|
285 |
|
public function __construct( |
162
|
|
|
Connection $connection, |
163
|
|
|
OutputWriter $outputWriter = null, |
164
|
|
|
MigrationFinderInterface $finder = null, |
165
|
|
|
QueryWriter $queryWriter = null |
166
|
|
|
) { |
167
|
285 |
|
$this->connection = $connection; |
168
|
285 |
|
$this->outputWriter = $outputWriter ?? new OutputWriter(); |
169
|
285 |
|
$this->migrationFinder = $finder ?? new RecursiveRegexFinder(); |
170
|
285 |
|
$this->queryWriter = $queryWriter; |
171
|
285 |
|
} |
172
|
|
|
|
173
|
|
|
/** |
174
|
|
|
* @return bool |
175
|
|
|
*/ |
176
|
22 |
|
public function areMigrationsOrganizedByYear() |
177
|
|
|
{ |
178
|
22 |
|
return $this->migrationsAreOrganizedByYear; |
179
|
|
|
} |
180
|
|
|
|
181
|
|
|
/** |
182
|
|
|
* @return bool |
183
|
|
|
*/ |
184
|
22 |
|
public function areMigrationsOrganizedByYearAndMonth() |
185
|
|
|
{ |
186
|
22 |
|
return $this->migrationsAreOrganizedByYearAndMonth; |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
/** |
190
|
|
|
* Validation that this instance has all the required properties configured |
191
|
|
|
* |
192
|
|
|
* @throws MigrationException |
193
|
|
|
*/ |
194
|
148 |
|
public function validate() |
195
|
|
|
{ |
196
|
148 |
|
if ( ! $this->migrationsNamespace) { |
197
|
10 |
|
throw MigrationException::migrationsNamespaceRequired(); |
198
|
|
|
} |
199
|
138 |
|
if ( ! $this->migrationsDirectory) { |
200
|
10 |
|
throw MigrationException::migrationsDirectoryRequired(); |
201
|
|
|
} |
202
|
128 |
|
} |
203
|
|
|
|
204
|
|
|
/** |
205
|
|
|
* Set the name of this set of migrations |
206
|
|
|
* |
207
|
|
|
* @param string $name The name of this set of migrations |
208
|
|
|
*/ |
209
|
63 |
|
public function setName($name) |
210
|
|
|
{ |
211
|
63 |
|
$this->name = $name; |
212
|
63 |
|
} |
213
|
|
|
|
214
|
|
|
/** |
215
|
|
|
* Returns the name of this set of migrations |
216
|
|
|
* |
217
|
|
|
* @return string $name The name of this set of migrations |
218
|
|
|
*/ |
219
|
16 |
|
public function getName() |
220
|
|
|
{ |
221
|
16 |
|
return $this->name; |
222
|
|
|
} |
223
|
|
|
|
224
|
|
|
/** |
225
|
|
|
* Sets the output writer. |
226
|
|
|
* |
227
|
|
|
* @param OutputWriter $outputWriter |
228
|
|
|
*/ |
229
|
5 |
|
public function setOutputWriter(OutputWriter $outputWriter) |
230
|
|
|
{ |
231
|
5 |
|
$this->outputWriter = $outputWriter; |
232
|
5 |
|
} |
233
|
|
|
|
234
|
|
|
/** |
235
|
|
|
* Returns the OutputWriter instance |
236
|
|
|
* |
237
|
|
|
* @return OutputWriter $outputWriter The OutputWriter instance |
238
|
|
|
*/ |
239
|
124 |
|
public function getOutputWriter() |
240
|
|
|
{ |
241
|
124 |
|
return $this->outputWriter; |
242
|
|
|
} |
243
|
|
|
|
244
|
|
|
/** |
245
|
|
|
* Returns a timestamp version as a formatted date |
246
|
|
|
* |
247
|
|
|
* @param string $version |
248
|
|
|
* |
249
|
|
|
* @return string The formatted version |
250
|
|
|
* @deprecated |
251
|
|
|
*/ |
252
|
14 |
|
public function formatVersion($version) |
253
|
|
|
{ |
254
|
14 |
|
return $this->getDateTime($version); |
255
|
|
|
} |
256
|
|
|
|
257
|
|
|
/** |
258
|
|
|
* Returns the datetime of a migration |
259
|
|
|
* |
260
|
|
|
* @param string $version |
261
|
|
|
* @return string |
262
|
|
|
*/ |
263
|
22 |
|
public function getDateTime($version) |
264
|
|
|
{ |
265
|
22 |
|
$datetime = str_replace('Version', '', $version); |
266
|
22 |
|
$datetime = \DateTime::createFromFormat('YmdHis', $datetime); |
267
|
|
|
|
268
|
22 |
|
if ($datetime === false) { |
269
|
8 |
|
return ''; |
270
|
|
|
} |
271
|
|
|
|
272
|
15 |
|
return $datetime->format('Y-m-d H:i:s'); |
273
|
|
|
} |
274
|
|
|
|
275
|
|
|
/** |
276
|
|
|
* Returns the Connection instance |
277
|
|
|
* |
278
|
|
|
* @return Connection $connection The Connection instance |
279
|
|
|
*/ |
280
|
127 |
|
public function getConnection() |
281
|
|
|
{ |
282
|
127 |
|
return $this->connection; |
283
|
|
|
} |
284
|
|
|
|
285
|
|
|
/** |
286
|
|
|
* Set the migration table name |
287
|
|
|
* |
288
|
|
|
* @param string $tableName The migration table name |
289
|
|
|
*/ |
290
|
85 |
|
public function setMigrationsTableName($tableName) |
291
|
|
|
{ |
292
|
85 |
|
$this->migrationsTableName = $tableName; |
293
|
85 |
|
} |
294
|
|
|
|
295
|
|
|
/** |
296
|
|
|
* Returns the migration table name |
297
|
|
|
* |
298
|
|
|
* @return string $migrationsTableName The migration table name |
299
|
|
|
*/ |
300
|
62 |
|
public function getMigrationsTableName() |
301
|
|
|
{ |
302
|
62 |
|
return $this->migrationsTableName; |
303
|
|
|
} |
304
|
|
|
|
305
|
|
|
/** |
306
|
|
|
* Set the migration column name |
307
|
|
|
* |
308
|
|
|
* @param string $columnName The migration column name |
309
|
|
|
*/ |
310
|
56 |
|
public function setMigrationsColumnName($columnName) |
311
|
|
|
{ |
312
|
56 |
|
$this->migrationsColumnName = $columnName; |
313
|
56 |
|
} |
314
|
|
|
|
315
|
|
|
/** |
316
|
|
|
* Returns the migration column name |
317
|
|
|
* |
318
|
|
|
* @return string $migrationsColumnName The migration column name |
319
|
|
|
*/ |
320
|
15 |
|
public function getMigrationsColumnName() |
321
|
|
|
{ |
322
|
15 |
|
return $this->migrationsColumnName; |
323
|
|
|
} |
324
|
|
|
|
325
|
|
|
/** |
326
|
|
|
* Returns the quoted migration column name |
327
|
|
|
* |
328
|
|
|
* @return string The quouted migration column name |
329
|
|
|
*/ |
330
|
70 |
|
public function getQuotedMigrationsColumnName() |
331
|
|
|
{ |
332
|
70 |
|
return $this->getMigrationsColumn()->getQuotedName($this->connection->getDatabasePlatform()); |
333
|
|
|
} |
334
|
|
|
|
335
|
|
|
/** |
336
|
|
|
* Set the new migrations directory where new migration classes are generated |
337
|
|
|
* |
338
|
|
|
* @param string $migrationsDirectory The new migrations directory |
339
|
|
|
*/ |
340
|
186 |
|
public function setMigrationsDirectory($migrationsDirectory) |
341
|
|
|
{ |
342
|
186 |
|
$this->migrationsDirectory = $migrationsDirectory; |
343
|
186 |
|
} |
344
|
|
|
|
345
|
|
|
/** |
346
|
|
|
* Returns the new migrations directory where new migration classes are generated |
347
|
|
|
* |
348
|
|
|
* @return string $migrationsDirectory The new migrations directory |
349
|
|
|
*/ |
350
|
66 |
|
public function getMigrationsDirectory() |
351
|
|
|
{ |
352
|
66 |
|
return $this->migrationsDirectory; |
353
|
|
|
} |
354
|
|
|
|
355
|
|
|
/** |
356
|
|
|
* Set the migrations namespace |
357
|
|
|
* |
358
|
|
|
* @param string $migrationsNamespace The migrations namespace |
359
|
|
|
*/ |
360
|
198 |
|
public function setMigrationsNamespace($migrationsNamespace) |
361
|
|
|
{ |
362
|
198 |
|
$this->migrationsNamespace = $migrationsNamespace; |
363
|
198 |
|
} |
364
|
|
|
|
365
|
|
|
/** |
366
|
|
|
* Returns the migrations namespace |
367
|
|
|
* |
368
|
|
|
* @return string $migrationsNamespace The migrations namespace |
369
|
|
|
*/ |
370
|
93 |
|
public function getMigrationsNamespace() |
371
|
|
|
{ |
372
|
93 |
|
return $this->migrationsNamespace; |
373
|
|
|
} |
374
|
|
|
|
375
|
|
|
/** |
376
|
|
|
* Returns the custom template path |
377
|
|
|
* |
378
|
|
|
* @return string $customTemplate The custom template path |
379
|
|
|
*/ |
380
|
9 |
|
public function getCustomTemplate() : ?string |
381
|
|
|
{ |
382
|
9 |
|
return $this->customTemplate; |
383
|
|
|
} |
384
|
|
|
|
385
|
|
|
/** |
386
|
|
|
* Set the custom template path |
387
|
|
|
* |
388
|
|
|
* @param string $customTemplate The custom template path |
389
|
|
|
*/ |
390
|
5 |
|
public function setCustomTemplate(?string $customTemplate) : void |
391
|
|
|
{ |
392
|
5 |
|
$this->customTemplate = $customTemplate; |
393
|
5 |
|
} |
394
|
|
|
|
395
|
|
|
/** |
396
|
|
|
* Set the implementation of the migration finder. |
397
|
|
|
* |
398
|
|
|
* @param MigrationFinderInterface $finder The new migration finder |
399
|
|
|
* @throws MigrationException |
400
|
|
|
*/ |
401
|
8 |
|
public function setMigrationsFinder(MigrationFinderInterface $finder) |
402
|
|
|
{ |
403
|
8 |
|
if (($this->migrationsAreOrganizedByYear || $this->migrationsAreOrganizedByYearAndMonth) |
404
|
8 |
|
&& ! ($finder instanceof MigrationDeepFinderInterface)) { |
405
|
4 |
|
throw MigrationException::configurationIncompatibleWithFinder( |
406
|
4 |
|
'organize-migrations', |
407
|
4 |
|
$finder |
408
|
|
|
); |
409
|
|
|
} |
410
|
|
|
|
411
|
4 |
|
$this->migrationFinder = $finder; |
412
|
4 |
|
} |
413
|
|
|
|
414
|
|
|
/** |
415
|
|
|
* Register migrations from a given directory. Recursively finds all files |
416
|
|
|
* with the pattern VersionYYYYMMDDHHMMSS.php as the filename and registers |
417
|
|
|
* them as migrations. |
418
|
|
|
* |
419
|
|
|
* @param string $path The root directory to where some migration classes live. |
420
|
|
|
* |
421
|
|
|
* @return Version[] The array of migrations registered. |
422
|
|
|
*/ |
423
|
104 |
|
public function registerMigrationsFromDirectory($path) |
424
|
|
|
{ |
425
|
104 |
|
$this->validate(); |
426
|
|
|
|
427
|
88 |
|
return $this->registerMigrations($this->findMigrations($path)); |
428
|
|
|
} |
429
|
|
|
|
430
|
|
|
/** |
431
|
|
|
* Register a single migration version to be executed by a AbstractMigration |
432
|
|
|
* class. |
433
|
|
|
* |
434
|
|
|
* @param string $version The version of the migration in the format YYYYMMDDHHMMSS. |
435
|
|
|
* @param string $class The migration class to execute for the version. |
436
|
|
|
* |
437
|
|
|
* @return Version |
438
|
|
|
* |
439
|
|
|
* @throws MigrationException |
440
|
|
|
*/ |
441
|
70 |
|
public function registerMigration($version, $class) |
442
|
|
|
{ |
443
|
70 |
|
$this->ensureMigrationClassExists($class); |
444
|
|
|
|
445
|
69 |
|
$version = (string) $version; |
446
|
69 |
|
$class = (string) $class; |
447
|
69 |
|
if (isset($this->migrations[$version])) { |
448
|
1 |
|
throw MigrationException::duplicateMigrationVersion($version, get_class($this->migrations[$version])); |
449
|
|
|
} |
450
|
69 |
|
$version = new Version($this, $version, $class); |
451
|
69 |
|
$this->migrations[$version->getVersion()] = $version; |
452
|
69 |
|
ksort($this->migrations, SORT_STRING); |
453
|
|
|
|
454
|
69 |
|
return $version; |
455
|
|
|
} |
456
|
|
|
|
457
|
|
|
/** |
458
|
|
|
* Register an array of migrations. Each key of the array is the version and |
459
|
|
|
* the value is the migration class name. |
460
|
|
|
* |
461
|
|
|
* |
462
|
|
|
* @param array $migrations |
463
|
|
|
* |
464
|
|
|
* @return Version[] |
465
|
|
|
*/ |
466
|
92 |
|
public function registerMigrations(array $migrations) |
467
|
|
|
{ |
468
|
92 |
|
$versions = []; |
469
|
92 |
|
foreach ($migrations as $version => $class) { |
470
|
35 |
|
$versions[] = $this->registerMigration($version, $class); |
471
|
|
|
} |
472
|
|
|
|
473
|
91 |
|
return $versions; |
474
|
|
|
} |
475
|
|
|
|
476
|
|
|
/** |
477
|
|
|
* Get the array of registered migration versions. |
478
|
|
|
* |
479
|
|
|
* @return Version[] $migrations |
480
|
|
|
*/ |
481
|
35 |
|
public function getMigrations() |
482
|
|
|
{ |
483
|
35 |
|
return $this->migrations; |
484
|
|
|
} |
485
|
|
|
|
486
|
|
|
/** |
487
|
|
|
* Returns the Version instance for a given version in the format YYYYMMDDHHMMSS. |
488
|
|
|
* |
489
|
|
|
* @param string $version The version string in the format YYYYMMDDHHMMSS. |
490
|
|
|
* |
491
|
|
|
* @return Version |
492
|
|
|
* |
493
|
|
|
* @throws MigrationException Throws exception if migration version does not exist. |
494
|
|
|
*/ |
495
|
21 |
|
public function getVersion($version) |
496
|
|
|
{ |
497
|
21 |
|
if (empty($this->migrations)) { |
498
|
7 |
|
$this->registerMigrationsFromDirectory($this->getMigrationsDirectory()); |
499
|
|
|
} |
500
|
|
|
|
501
|
21 |
|
if ( ! isset($this->migrations[$version])) { |
502
|
2 |
|
throw MigrationException::unknownMigrationVersion($version); |
503
|
|
|
} |
504
|
|
|
|
505
|
19 |
|
return $this->migrations[$version]; |
506
|
|
|
} |
507
|
|
|
|
508
|
|
|
/** |
509
|
|
|
* Check if a version exists. |
510
|
|
|
* |
511
|
|
|
* @param string $version |
512
|
|
|
* |
513
|
|
|
* @return boolean |
514
|
|
|
*/ |
515
|
22 |
|
public function hasVersion($version) |
516
|
|
|
{ |
517
|
22 |
|
if (empty($this->migrations)) { |
518
|
4 |
|
$this->registerMigrationsFromDirectory($this->getMigrationsDirectory()); |
519
|
|
|
} |
520
|
|
|
|
521
|
20 |
|
return isset($this->migrations[$version]); |
522
|
|
|
} |
523
|
|
|
|
524
|
|
|
/** |
525
|
|
|
* Check if a version has been migrated or not yet |
526
|
|
|
* |
527
|
|
|
* @param Version $version |
528
|
|
|
* |
529
|
|
|
* @return boolean |
530
|
|
|
*/ |
531
|
21 |
|
public function hasVersionMigrated(Version $version) |
532
|
|
|
{ |
533
|
21 |
|
$this->connect(); |
534
|
21 |
|
$this->createMigrationTable(); |
535
|
|
|
|
536
|
21 |
|
$version = $this->connection->fetchColumn( |
537
|
21 |
|
"SELECT " . $this->getQuotedMigrationsColumnName() . " FROM " . $this->migrationsTableName . " WHERE " . $this->getQuotedMigrationsColumnName() . " = ?", |
538
|
21 |
|
[$version->getVersion()] |
539
|
|
|
); |
540
|
|
|
|
541
|
21 |
|
return $version !== false; |
542
|
|
|
} |
543
|
|
|
|
544
|
|
|
/** |
545
|
|
|
* Returns all migrated versions from the versions table, in an array. |
546
|
|
|
* |
547
|
|
|
* @return Version[] |
548
|
|
|
*/ |
549
|
42 |
|
public function getMigratedVersions() |
550
|
|
|
{ |
551
|
42 |
|
$this->createMigrationTable(); |
552
|
|
|
|
553
|
42 |
|
if ( ! $this->migrationTableCreated && $this->isDryRun) { |
554
|
1 |
|
return []; |
555
|
|
|
} |
556
|
|
|
|
557
|
42 |
|
$this->connect(); |
558
|
|
|
|
559
|
42 |
|
$ret = $this->connection->fetchAll("SELECT " . $this->getQuotedMigrationsColumnName() . " FROM " . $this->migrationsTableName); |
560
|
|
|
|
561
|
42 |
|
return array_map('current', $ret); |
562
|
|
|
} |
563
|
|
|
|
564
|
|
|
/** |
565
|
|
|
* Returns an array of available migration version numbers. |
566
|
|
|
* |
567
|
|
|
* @return array |
568
|
|
|
*/ |
569
|
16 |
|
public function getAvailableVersions() |
570
|
|
|
{ |
571
|
16 |
|
$availableVersions = []; |
572
|
|
|
|
573
|
16 |
|
if (empty($this->migrations)) { |
574
|
3 |
|
$this->registerMigrationsFromDirectory($this->getMigrationsDirectory()); |
575
|
|
|
} |
576
|
|
|
|
577
|
14 |
|
foreach ($this->migrations as $migration) { |
578
|
14 |
|
$availableVersions[] = $migration->getVersion(); |
579
|
|
|
} |
580
|
|
|
|
581
|
14 |
|
return $availableVersions; |
582
|
|
|
} |
583
|
|
|
|
584
|
|
|
/** |
585
|
|
|
* Returns the current migrated version from the versions table. |
586
|
|
|
* |
587
|
|
|
* @return string |
588
|
|
|
*/ |
589
|
44 |
|
public function getCurrentVersion() |
590
|
|
|
{ |
591
|
44 |
|
$this->createMigrationTable(); |
592
|
|
|
|
593
|
42 |
|
if ( ! $this->migrationTableCreated && $this->isDryRun) { |
594
|
1 |
|
return '0'; |
595
|
|
|
} |
596
|
|
|
|
597
|
42 |
|
$this->connect(); |
598
|
|
|
|
599
|
42 |
|
if (empty($this->migrations)) { |
600
|
16 |
|
$this->registerMigrationsFromDirectory($this->getMigrationsDirectory()); |
601
|
|
|
} |
602
|
|
|
|
603
|
42 |
|
$where = null; |
604
|
42 |
|
if ( ! empty($this->migrations)) { |
605
|
38 |
|
$migratedVersions = []; |
606
|
38 |
|
foreach ($this->migrations as $migration) { |
607
|
38 |
|
$migratedVersions[] = sprintf("'%s'", $migration->getVersion()); |
608
|
|
|
} |
609
|
38 |
|
$where = " WHERE " . $this->getQuotedMigrationsColumnName() . " IN (" . implode(', ', $migratedVersions) . ")"; |
610
|
|
|
} |
611
|
|
|
|
612
|
42 |
|
$sql = sprintf( |
613
|
42 |
|
"SELECT %s FROM %s%s ORDER BY %s DESC", |
614
|
42 |
|
$this->getQuotedMigrationsColumnName(), |
615
|
42 |
|
$this->migrationsTableName, |
616
|
42 |
|
$where, |
617
|
42 |
|
$this->getQuotedMigrationsColumnName() |
618
|
|
|
); |
619
|
|
|
|
620
|
42 |
|
$sql = $this->connection->getDatabasePlatform()->modifyLimitQuery($sql, 1); |
621
|
42 |
|
$result = $this->connection->fetchColumn($sql); |
622
|
|
|
|
623
|
42 |
|
return $result !== false ? (string) $result : '0'; |
624
|
|
|
} |
625
|
|
|
|
626
|
|
|
/** |
627
|
|
|
* Returns the version prior to the current version. |
628
|
|
|
* |
629
|
|
|
* @return string|null A version string, or null if the current version is |
630
|
|
|
* the first. |
631
|
|
|
*/ |
632
|
10 |
|
public function getPrevVersion() |
633
|
|
|
{ |
634
|
10 |
|
return $this->getRelativeVersion($this->getCurrentVersion(), -1); |
635
|
|
|
} |
636
|
|
|
|
637
|
|
|
/** |
638
|
|
|
* Returns the version following the current version. |
639
|
|
|
* |
640
|
|
|
* @return string|null A version string, or null if the current version is |
641
|
|
|
* the latest. |
642
|
|
|
*/ |
643
|
11 |
|
public function getNextVersion() |
644
|
|
|
{ |
645
|
11 |
|
return $this->getRelativeVersion($this->getCurrentVersion(), 1); |
646
|
|
|
} |
647
|
|
|
|
648
|
|
|
/** |
649
|
|
|
* Returns the version with the specified offset to the specified version. |
650
|
|
|
* |
651
|
|
|
* @param string $version |
652
|
|
|
* @param string $delta |
653
|
|
|
* @return null|string A version string, or null if the specified version |
654
|
|
|
* is unknown or the specified delta is not within the |
655
|
|
|
* list of available versions. |
656
|
|
|
*/ |
657
|
17 |
|
public function getRelativeVersion($version, $delta) |
658
|
|
|
{ |
659
|
17 |
|
if (empty($this->migrations)) { |
660
|
5 |
|
$this->registerMigrationsFromDirectory($this->getMigrationsDirectory()); |
661
|
|
|
} |
662
|
|
|
|
663
|
15 |
|
$versions = array_map('strval', array_keys($this->migrations)); |
664
|
15 |
|
array_unshift($versions, '0'); |
665
|
15 |
|
$offset = array_search((string) $version, $versions, true); |
666
|
15 |
|
if ($offset === false || ! isset($versions[$offset + $delta])) { |
667
|
|
|
// Unknown version or delta out of bounds. |
668
|
11 |
|
return null; |
669
|
|
|
} |
670
|
|
|
|
671
|
13 |
|
return $versions[$offset + $delta]; |
672
|
|
|
} |
673
|
|
|
|
674
|
|
|
/** |
675
|
|
|
* Returns the version with the specified to the current version. |
676
|
|
|
* |
677
|
|
|
* @param string $delta |
678
|
|
|
* @return null|string A version string, or null if the specified delta is |
679
|
|
|
* not within the list of available versions. |
680
|
|
|
*/ |
681
|
1 |
|
public function getDeltaVersion($delta) |
682
|
|
|
{ |
683
|
1 |
|
$symbol = substr($delta, 0, 1); |
684
|
1 |
|
$number = (int) substr($delta, 1); |
685
|
|
|
|
686
|
1 |
|
if ($number <= 0) { |
687
|
|
|
return null; |
688
|
|
|
} |
689
|
|
|
|
690
|
1 |
|
if ($symbol == "+" || $symbol == "-") { |
691
|
1 |
|
return $this->getRelativeVersion($this->getCurrentVersion(), (int) $delta); |
692
|
|
|
} |
693
|
|
|
|
694
|
|
|
return null; |
695
|
|
|
} |
696
|
|
|
|
697
|
|
|
/** |
698
|
|
|
* Returns the version number from an alias. |
699
|
|
|
* |
700
|
|
|
* Supported aliases are: |
701
|
|
|
* - first: The very first version before any migrations have been run. |
702
|
|
|
* - current: The current version. |
703
|
|
|
* - prev: The version prior to the current version. |
704
|
|
|
* - next: The version following the current version. |
705
|
|
|
* - latest: The latest available version. |
706
|
|
|
* |
707
|
|
|
* If an existing version number is specified, it is returned verbatimly. |
708
|
|
|
* |
709
|
|
|
* @param string $alias |
710
|
|
|
* @return null|string A version number, or null if the specified alias |
711
|
|
|
* does not map to an existing version, e.g. if "next" |
712
|
|
|
* is passed but the current version is already the |
713
|
|
|
* latest. |
714
|
|
|
*/ |
715
|
9 |
|
public function resolveVersionAlias($alias) |
716
|
|
|
{ |
717
|
9 |
|
if ($this->hasVersion($alias)) { |
718
|
1 |
|
return $alias; |
719
|
|
|
} |
720
|
|
|
switch ($alias) { |
721
|
9 |
|
case 'first': |
|
|
|
|
722
|
1 |
|
return '0'; |
723
|
9 |
|
case 'current': |
|
|
|
|
724
|
9 |
|
return $this->getCurrentVersion(); |
725
|
9 |
|
case 'prev': |
|
|
|
|
726
|
9 |
|
return $this->getPrevVersion(); |
727
|
9 |
|
case 'next': |
|
|
|
|
728
|
9 |
|
return $this->getNextVersion(); |
729
|
9 |
|
case 'latest': |
730
|
9 |
|
return $this->getLatestVersion(); |
731
|
|
|
default: |
732
|
1 |
|
if (substr($alias, 0, 7) == 'current') { |
733
|
|
|
return $this->getDeltaVersion(substr($alias, 7)); |
734
|
|
|
} |
735
|
1 |
|
return null; |
736
|
|
|
} |
737
|
|
|
} |
738
|
|
|
|
739
|
|
|
/** |
740
|
|
|
* Returns the total number of executed migration versions |
741
|
|
|
* |
742
|
|
|
* @return integer |
743
|
|
|
*/ |
744
|
1 |
|
public function getNumberOfExecutedMigrations() |
745
|
|
|
{ |
746
|
1 |
|
$this->connect(); |
747
|
1 |
|
$this->createMigrationTable(); |
748
|
|
|
|
749
|
1 |
|
$result = $this->connection->fetchColumn("SELECT COUNT(" . $this->getQuotedMigrationsColumnName() . ") FROM " . $this->migrationsTableName); |
750
|
|
|
|
751
|
1 |
|
return $result !== false ? $result : 0; |
752
|
|
|
} |
753
|
|
|
|
754
|
|
|
/** |
755
|
|
|
* Returns the total number of available migration versions |
756
|
|
|
* |
757
|
|
|
* @return integer |
758
|
|
|
*/ |
759
|
5 |
|
public function getNumberOfAvailableMigrations() |
760
|
|
|
{ |
761
|
5 |
|
if (empty($this->migrations)) { |
762
|
4 |
|
$this->registerMigrationsFromDirectory($this->getMigrationsDirectory()); |
763
|
|
|
} |
764
|
|
|
|
765
|
3 |
|
return count($this->migrations); |
766
|
|
|
} |
767
|
|
|
|
768
|
|
|
/** |
769
|
|
|
* Returns the latest available migration version. |
770
|
|
|
* |
771
|
|
|
* @return string The version string in the format YYYYMMDDHHMMSS. |
772
|
|
|
*/ |
773
|
28 |
|
public function getLatestVersion() |
774
|
|
|
{ |
775
|
28 |
|
if (empty($this->migrations)) { |
776
|
7 |
|
$this->registerMigrationsFromDirectory($this->getMigrationsDirectory()); |
777
|
|
|
} |
778
|
|
|
|
779
|
26 |
|
$versions = array_keys($this->migrations); |
780
|
26 |
|
$latest = end($versions); |
781
|
|
|
|
782
|
26 |
|
return $latest !== false ? (string) $latest : '0'; |
783
|
|
|
} |
784
|
|
|
|
785
|
|
|
/** |
786
|
|
|
* Create the migration table to track migrations with. |
787
|
|
|
* |
788
|
|
|
* @return boolean Whether or not the table was created. |
789
|
|
|
*/ |
790
|
66 |
|
public function createMigrationTable() |
791
|
|
|
{ |
792
|
66 |
|
$this->validate(); |
793
|
|
|
|
794
|
64 |
|
if ($this->migrationTableCreated) { |
795
|
52 |
|
return false; |
796
|
|
|
} |
797
|
|
|
|
798
|
64 |
|
$this->connect(); |
799
|
64 |
|
if ($this->connection->getSchemaManager()->tablesExist([$this->migrationsTableName])) { |
800
|
4 |
|
$this->migrationTableCreated = true; |
801
|
|
|
|
802
|
4 |
|
return false; |
803
|
|
|
} |
804
|
|
|
|
805
|
63 |
|
if ($this->isDryRun) { |
806
|
1 |
|
return false; |
807
|
|
|
} |
808
|
|
|
|
809
|
|
|
$columns = [ |
810
|
63 |
|
$this->migrationsColumnName => $this->getMigrationsColumn(), |
811
|
|
|
]; |
812
|
63 |
|
$table = new Table($this->migrationsTableName, $columns); |
813
|
63 |
|
$table->setPrimaryKey([$this->migrationsColumnName]); |
814
|
63 |
|
$this->connection->getSchemaManager()->createTable($table); |
815
|
|
|
|
816
|
63 |
|
$this->migrationTableCreated = true; |
817
|
|
|
|
818
|
63 |
|
return true; |
819
|
|
|
} |
820
|
|
|
|
821
|
|
|
/** |
822
|
|
|
* Returns the array of migrations to executed based on the given direction |
823
|
|
|
* and target version number. |
824
|
|
|
* |
825
|
|
|
* @param string $direction The direction we are migrating. |
826
|
|
|
* @param string $to The version to migrate to. |
827
|
|
|
* |
828
|
|
|
* @return Version[] $migrations The array of migrations we can execute. |
829
|
|
|
*/ |
830
|
39 |
|
public function getMigrationsToExecute($direction, $to) |
831
|
|
|
{ |
832
|
39 |
|
if (empty($this->migrations)) { |
833
|
11 |
|
$this->registerMigrationsFromDirectory($this->getMigrationsDirectory()); |
834
|
|
|
} |
835
|
|
|
|
836
|
33 |
|
if ($direction === Version::DIRECTION_DOWN) { |
837
|
7 |
|
if (count($this->migrations)) { |
838
|
7 |
|
$allVersions = array_reverse(array_keys($this->migrations)); |
839
|
7 |
|
$classes = array_reverse(array_values($this->migrations)); |
840
|
7 |
|
$allVersions = array_combine($allVersions, $classes); |
841
|
|
|
} else { |
842
|
7 |
|
$allVersions = []; |
843
|
|
|
} |
844
|
|
|
} else { |
845
|
31 |
|
$allVersions = $this->migrations; |
846
|
|
|
} |
847
|
33 |
|
$versions = []; |
848
|
33 |
|
$migrated = $this->getMigratedVersions(); |
849
|
33 |
|
foreach ($allVersions as $version) { |
850
|
31 |
|
if ($this->shouldExecuteMigration($direction, $version, $to, $migrated)) { |
851
|
31 |
|
$versions[$version->getVersion()] = $version; |
852
|
|
|
} |
853
|
|
|
} |
854
|
|
|
|
855
|
33 |
|
return $versions; |
856
|
|
|
} |
857
|
|
|
|
858
|
|
|
/** |
859
|
|
|
* Use the connection's event manager to emit an event. |
860
|
|
|
* |
861
|
|
|
* @param string $eventName The event to emit. |
862
|
|
|
* @param EventArgs $args The event args instance to emit. |
863
|
|
|
*/ |
864
|
51 |
|
public function dispatchEvent($eventName, EventArgs $args = null) |
865
|
|
|
{ |
866
|
51 |
|
$this->connection->getEventManager()->dispatchEvent($eventName, $args); |
867
|
51 |
|
} |
868
|
|
|
|
869
|
|
|
/** |
870
|
|
|
* Find all the migrations in a given directory. |
871
|
|
|
* |
872
|
|
|
* @param string $path the directory to search. |
873
|
|
|
* @return array |
874
|
|
|
*/ |
875
|
88 |
|
protected function findMigrations($path) |
876
|
|
|
{ |
877
|
88 |
|
return $this->migrationFinder->findMigrations($path, $this->getMigrationsNamespace()); |
878
|
|
|
} |
879
|
|
|
|
880
|
|
|
/** |
881
|
|
|
* @param bool $migrationsAreOrganizedByYear |
882
|
|
|
* @throws MigrationException |
883
|
|
|
*/ |
884
|
9 |
|
public function setMigrationsAreOrganizedByYear($migrationsAreOrganizedByYear = true) |
885
|
|
|
{ |
886
|
9 |
|
$this->ensureOrganizeMigrationsIsCompatibleWithFinder(); |
887
|
|
|
|
888
|
5 |
|
$this->migrationsAreOrganizedByYear = $migrationsAreOrganizedByYear; |
889
|
5 |
|
} |
890
|
|
|
|
891
|
|
|
/** |
892
|
|
|
* @param bool $migrationsAreOrganizedByYearAndMonth |
893
|
|
|
* @throws MigrationException |
894
|
|
|
*/ |
895
|
10 |
|
public function setMigrationsAreOrganizedByYearAndMonth($migrationsAreOrganizedByYearAndMonth = true) |
896
|
|
|
{ |
897
|
10 |
|
$this->ensureOrganizeMigrationsIsCompatibleWithFinder(); |
898
|
|
|
|
899
|
10 |
|
$this->migrationsAreOrganizedByYear = $migrationsAreOrganizedByYearAndMonth; |
900
|
10 |
|
$this->migrationsAreOrganizedByYearAndMonth = $migrationsAreOrganizedByYearAndMonth; |
901
|
10 |
|
} |
902
|
|
|
|
903
|
|
|
/** |
904
|
|
|
* Generate a new migration version. A version is (usually) a datetime string. |
905
|
|
|
* |
906
|
|
|
* @param \DateTimeInterface|null $now Defaults to the current time in UTC |
907
|
|
|
* @return string The newly generated version |
908
|
|
|
*/ |
909
|
9 |
|
public function generateVersionNumber(\DateTimeInterface $now = null) |
910
|
|
|
{ |
911
|
9 |
|
$now = $now ?: new \DateTime('now', new \DateTimeZone('UTC')); |
912
|
|
|
|
913
|
9 |
|
return $now->format(self::VERSION_FORMAT); |
914
|
|
|
} |
915
|
|
|
|
916
|
|
|
/** |
917
|
|
|
* Explicitely opens the database connection. This is done to play nice |
918
|
|
|
* with DBAL's MasterSlaveConnection. Which, in some cases, connects to a |
919
|
|
|
* follower when fetching the executed migrations. If a follower is lagging |
920
|
|
|
* significantly behind that means the migrations system may see unexecuted |
921
|
|
|
* migrations that were actually executed earlier. |
922
|
|
|
* |
923
|
|
|
* @return bool The same value returned from the `connect` method |
924
|
|
|
*/ |
925
|
64 |
|
protected function connect() |
926
|
|
|
{ |
927
|
64 |
|
if ($this->connection instanceof MasterSlaveConnection) { |
928
|
1 |
|
return $this->connection->connect('master'); |
929
|
|
|
} |
930
|
|
|
|
931
|
63 |
|
return $this->connection->connect(); |
932
|
|
|
} |
933
|
|
|
|
934
|
|
|
/** |
935
|
|
|
* @throws MigrationException |
936
|
|
|
*/ |
937
|
19 |
|
private function ensureOrganizeMigrationsIsCompatibleWithFinder() |
938
|
|
|
{ |
939
|
19 |
|
if ( ! ($this->migrationFinder instanceof MigrationDeepFinderInterface)) { |
940
|
4 |
|
throw MigrationException::configurationIncompatibleWithFinder( |
941
|
4 |
|
'organize-migrations', |
942
|
4 |
|
$this->migrationFinder |
943
|
|
|
); |
944
|
|
|
} |
945
|
15 |
|
} |
946
|
|
|
|
947
|
|
|
/** |
948
|
|
|
* Check if we should execute a migration for a given direction and target |
949
|
|
|
* migration version. |
950
|
|
|
* |
951
|
|
|
* @param string $direction The direction we are migrating. |
952
|
|
|
* @param Version $version The Version instance to check. |
953
|
|
|
* @param string $to The version we are migrating to. |
954
|
|
|
* @param array $migrated Migrated versions array. |
955
|
|
|
* |
956
|
|
|
* @return boolean |
957
|
|
|
*/ |
958
|
31 |
|
private function shouldExecuteMigration($direction, Version $version, $to, $migrated) |
959
|
|
|
{ |
960
|
31 |
|
if ($direction === Version::DIRECTION_DOWN) { |
961
|
7 |
|
if ( ! in_array($version->getVersion(), $migrated, true)) { |
962
|
4 |
|
return false; |
963
|
|
|
} |
964
|
|
|
|
965
|
5 |
|
return $version->getVersion() > $to; |
966
|
|
|
} |
967
|
|
|
|
968
|
29 |
|
if ($direction === Version::DIRECTION_UP) { |
969
|
29 |
|
if (in_array($version->getVersion(), $migrated, true)) { |
970
|
8 |
|
return false; |
971
|
|
|
} |
972
|
|
|
|
973
|
28 |
|
return $version->getVersion() <= $to; |
974
|
|
|
} |
975
|
|
|
} |
976
|
|
|
|
977
|
|
|
/** |
978
|
|
|
* @param string $class |
979
|
|
|
* @throws MigrationException |
980
|
|
|
*/ |
981
|
70 |
|
private function ensureMigrationClassExists($class) |
982
|
|
|
{ |
983
|
70 |
|
if ( ! class_exists($class)) { |
984
|
1 |
|
throw MigrationException::migrationClassNotFound($class, $this->getMigrationsNamespace()); |
985
|
|
|
} |
986
|
69 |
|
} |
987
|
|
|
|
988
|
8 |
|
public function getQueryWriter() : QueryWriter |
989
|
|
|
{ |
990
|
8 |
|
if ($this->queryWriter === null) { |
991
|
7 |
|
$this->queryWriter = new FileQueryWriter( |
992
|
7 |
|
$this->getQuotedMigrationsColumnName(), |
993
|
7 |
|
$this->migrationsTableName, |
994
|
7 |
|
$this->outputWriter |
995
|
|
|
); |
996
|
|
|
} |
997
|
|
|
|
998
|
8 |
|
return $this->queryWriter; |
999
|
|
|
} |
1000
|
|
|
|
1001
|
|
|
/** |
1002
|
|
|
* @param bool $isDryRun |
1003
|
|
|
*/ |
1004
|
2 |
|
public function setIsDryRun($isDryRun) |
1005
|
|
|
{ |
1006
|
2 |
|
$this->isDryRun = $isDryRun; |
1007
|
2 |
|
} |
1008
|
|
|
|
1009
|
70 |
|
private function getMigrationsColumn(): Column |
1010
|
|
|
{ |
1011
|
70 |
|
return new Column($this->migrationsColumnName, Type::getType('string'), ['length' => 255]); |
1012
|
|
|
} |
1013
|
|
|
} |
1014
|
|
|
|
As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next
break
.There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.
To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.