Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions docs/en/getting-started/running-and-managing-migrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,27 @@ in CI.
e.g. `{"app": [...], "PluginName": [...]}`. `--all` cannot be combined with
`--plugin` or `--cleanup`.

### Validating Migration Files

Migration classes are only loaded when the migration they contain is executed.
A migration file that cannot be loaded, for example one still extending the
removed `Migrations\AbstractMigration` class, will therefore not fail `status`
or the `PendingMigrationsMiddleware`. The `--validate` option loads every
migration class and reports the ones that cannot be loaded:

```bash
bin/cake migrations status --validate
```

When any migration cannot be loaded, the offending versions are printed to
stderr and the command exits with `1`, which makes it a useful CI check.
Otherwise the regular status output follows. The option can be combined with
`--all` to validate the app and every loaded plugin in one call.

The same check is available programmatically through
`Manager::validateMigrations()`, which returns the error messages indexed by
migration version.

### Cleaning Up Missing Migrations

Sometimes migration files may be deleted from the filesystem but still exist in
Expand Down
60 changes: 60 additions & 0 deletions src/Command/StatusCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
use Cake\Core\Plugin;
use Migrations\Config\ConfigInterface;
use Migrations\Db\Adapter\UnifiedMigrationsTableStorage;
use Migrations\Migration\Manager;
use Migrations\Migration\ManagerFactory;

/**
Expand Down Expand Up @@ -78,6 +79,8 @@ protected function buildOptionParser(ConsoleOptionParser $parser): ConsoleOption
'Add <info>-v</info> to also print the per-section migration tables.',
'<info>migrations status --cleanup</info>',
'Remove *MISSING* migrations from the migration tracking table',
'<info>migrations status --validate</info>',
'Load every migration class and fail if any of them cannot be loaded.',
])->addOption('plugin', [
'short' => 'p',
'help' => 'The plugin to run migrations for',
Expand All @@ -104,6 +107,11 @@ protected function buildOptionParser(ConsoleOptionParser $parser): ConsoleOption
'help' => 'Remove MISSING migrations from the migration tracking table',
'boolean' => true,
'default' => false,
])->addOption('validate', [
'help' => 'Load every migration class and fail if any of them cannot be loaded. '
. 'Migration classes are otherwise only loaded when they are executed.',
'boolean' => true,
'default' => false,
]);

return $parser;
Expand Down Expand Up @@ -146,6 +154,20 @@ public function execute(Arguments $args, ConsoleIo $io): ?int
]);
$manager = $factory->createManager($io);

if ($args->getOption('validate')) {
/** @var string|null $plugin */
$plugin = $args->getOption('plugin');
if (!$this->validateMigrations($manager, $io, $plugin ?? 'app')) {
return Command::CODE_ERROR;
}
if ($format !== 'json') {
$io->out(sprintf(
'<success>All %d migrations can be loaded.</success>',
count($manager->getMigrationVersions()),
));
}
}

if ($clean) {
$removed = $manager->cleanupMissingMigrations();
if ($removed === 0) {
Expand Down Expand Up @@ -200,6 +222,8 @@ protected function executeAll(Arguments $args, ConsoleIo $io, ?string $format):
}

$verbose = (bool)$args->getOption('verbose');
$validate = (bool)$args->getOption('validate');
$validationFailed = false;
$jsonResults = [];
$summary = [];
$exitCode = Command::CODE_SUCCESS;
Expand All @@ -212,6 +236,11 @@ protected function executeAll(Arguments $args, ConsoleIo $io, ?string $format):
'dry-run' => $args->getOption('dry-run'),
]);
$manager = $factory->createManager($io);

if ($validate && !$this->validateMigrations($manager, $io, $label)) {
$validationFailed = true;
}

$migrations = $manager->printStatus($format);

$sectionExit = $this->statusExitCode($migrations);
Expand Down Expand Up @@ -241,6 +270,10 @@ protected function executeAll(Arguments $args, ConsoleIo $io, ?string $format):
$this->display($migrations, $io, $manager->getSchemaTableName());
}

if ($validationFailed) {
return Command::CODE_ERROR;
}

if ($format === 'json') {
$flags = 0;
if ($verbose) {
Expand All @@ -256,6 +289,33 @@ protected function executeAll(Arguments $args, ConsoleIo $io, ?string $format):
return $exitCode;
}

/**
* Load every migration class and print the ones that could not be loaded.
*
* @param \Migrations\Migration\Manager $manager The manager to load migrations with.
* @param \Cake\Console\ConsoleIo $io The console io.
* @param string $label The section the migrations belong to.
* @return bool True when every migration class could be loaded.
*/
protected function validateMigrations(Manager $manager, ConsoleIo $io, string $label): bool
{
$errors = $manager->validateMigrations();
if (!$errors) {
return true;
}

$io->err(sprintf(
'<error>%s: %d migration(s) could not be loaded:</error>',
$label === 'app' ? 'APP' : $label,
count($errors),
));
foreach ($errors as $version => $message) {
$io->err(sprintf(' - %d: %s', $version, $message));
}

return false;
}

/**
* Count actionable items (down + missing) in a section's migrations array.
*
Expand Down
10 changes: 4 additions & 6 deletions src/Middleware/PendingMigrationsMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,8 @@ protected function checkAppMigrations(): bool

$manager = $this->getManager($this->_config);

$migrations = $manager->getMigrations();
foreach ($migrations as $migration) {
if (!$manager->isMigrated($migration->getVersion())) {
foreach ($manager->getMigrationVersions() as $version) {
if (!$manager->isMigrated($version)) {
return false;
}
}
Expand Down Expand Up @@ -146,9 +145,8 @@ protected function checkPluginMigrations(string $plugin): bool
$config['environment']['migration_table'] = $table;
$manager = $this->getManager($config);

$migrations = $manager->getMigrations();
foreach ($migrations as $migration) {
if (!$manager->isMigrated($migration->getVersion())) {
foreach ($manager->getMigrationVersions() as $version) {
if (!$manager->isMigrated($version)) {
return false;
}
}
Expand Down
Loading
Loading