From e8ad9e3057c93b42ee5339b696ca333799fb7548 Mon Sep 17 00:00:00 2001 From: Sergei Tigrov Date: Tue, 21 Nov 2023 14:29:37 +0700 Subject: [PATCH] Support `--path` and `--namespace` options for `migrate:redo` command (#235) --- src/Command/RedoCommand.php | 45 ++++-- .../Command/AbstractRedoCommandTest.php | 138 ++++++++++++++++++ 2 files changed, 173 insertions(+), 10 deletions(-) diff --git a/src/Command/RedoCommand.php b/src/Command/RedoCommand.php index 68f7fb4..984fc21 100644 --- a/src/Command/RedoCommand.php +++ b/src/Command/RedoCommand.php @@ -30,9 +30,11 @@ * For example: * * ```shell - * ./yii migrate:redo # redo the last applied migration - * ./yii migrate:redo --limit=3 # redo last 3 applied migrations - * ./yii migrate:redo --all # redo all migrations + * ./yii migrate:redo # redo the last applied migration + * ./yii migrate:redo --limit=3 # redo last 3 applied migrations + * ./yii migrate:redo --all # redo all migrations + * ./yii migrate:redo --path=@vendor/yiisoft/rbac-db/migrations # redo the last migration from the directory + * ./yii migrate:redo --namespace=Yiisoft\\Rbac\\Db\\Migrations # redo the last migration from the namespace * ``` */ #[AsCommand('migrate:redo', 'Redoes the last few migrations.')] @@ -51,7 +53,9 @@ protected function configure(): void { $this ->addOption('limit', 'l', InputOption::VALUE_REQUIRED, 'Number of migrations to redo.', 1) - ->addOption('all', 'a', InputOption::VALUE_NONE, 'All migrations.'); + ->addOption('all', 'a', InputOption::VALUE_NONE, 'Redo all migrations.') + ->addOption('path', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Path to migrations to redo.') + ->addOption('namespace', 'ns', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Namespace of migrations to redo.'); } protected function execute(InputInterface $input, OutputInterface $output): int @@ -75,15 +79,36 @@ protected function execute(InputInterface $input, OutputInterface $output): int return Command::INVALID; } - $migrations = $this->migrator->getHistory($limit); + /** @psalm-var string[] $paths */ + $paths = $input->getOption('path'); + /** @psalm-var string[] $namespaces */ + $namespaces = $input->getOption('namespace'); - if (empty($migrations)) { - $io->warning('No migration has been done before.'); + if (!empty($paths) || !empty($namespaces)) { + $migrations = $this->migrator->getHistory(); + $migrations = array_keys($migrations); + $migrations = $this->migrationService->filterMigrations($migrations, $namespaces, $paths); - return Command::FAILURE; - } + if (empty($migrations)) { + $io->warning('No applied migrations found.'); + + return Command::FAILURE; + } + + if ($limit !== null) { + $migrations = array_slice($migrations, 0, $limit); + } + } else { + $migrations = $this->migrator->getHistory($limit); - $migrations = array_keys($migrations); + if (empty($migrations)) { + $io->warning('No migration has been done before.'); + + return Command::FAILURE; + } + + $migrations = array_keys($migrations); + } $n = count($migrations); $migrationWord = $n === 1 ? 'migration' : 'migrations'; diff --git a/tests/Common/Command/AbstractRedoCommandTest.php b/tests/Common/Command/AbstractRedoCommandTest.php index e01d453..1425319 100644 --- a/tests/Common/Command/AbstractRedoCommandTest.php +++ b/tests/Common/Command/AbstractRedoCommandTest.php @@ -17,7 +17,10 @@ use Yiisoft\Db\Migration\Tests\Support\Helper\CommandHelper; use Yiisoft\Db\Migration\Tests\Support\Helper\DbHelper; use Yiisoft\Db\Migration\Tests\Support\Helper\MigrationHelper; +use Yiisoft\Db\Migration\Tests\Support\Migrations\M231015155500ExecuteSql; use Yiisoft\Db\Migration\Tests\Support\Migrations\M231017150317EmptyDown; +use Yiisoft\Db\Migration\Tests\Support\MigrationsExtra\M231108183919Empty; +use Yiisoft\Db\Migration\Tests\Support\MigrationsExtra\M231108183919Empty2; use Yiisoft\Db\Migration\Tests\Support\Stub\StubMigration; abstract class AbstractRedoCommandTest extends TestCase @@ -326,6 +329,141 @@ public function testRevertedButNotApplied(): void DbHelper::dropTable($db, 'chapter'); } + public function testOptionsNamespaceAndPath(): void + { + MigrationHelper::useMigrationsPath($this->container); + + $migrator = $this->container->get(Migrator::class); + $migrator->up(new M231015155500ExecuteSql()); + $migrator->up(new M231108183919Empty()); + + MigrationHelper::useMigrationsNamespace($this->container); + MigrationHelper::createAndApplyMigration( + $this->container, + 'Create_User', + 'table', + 'user', + ['name:string(50)'], + ); + + $command = $this->createCommand($this->container); + $options = [ + '--namespace' => ['Yiisoft\Db\Migration\Tests\Support\Migrations'], + '-ns' => ['Yiisoft\Db\Migration\Tests\Support\Migrations'], + '--path' => [dirname(__DIR__, 2) . '/Support/Migrations'], + ]; + + foreach ($options as $option => $value) { + $exitCode = $command->setInputs(['no'])->execute([$option => $value, '-a' => true]); + $output = $command->getDisplay(true); + + $this->assertSame(Command::SUCCESS, $exitCode); + $this->assertStringContainsString('Total 1 migration to be redone:', $output); + $this->assertStringContainsString('1. ' . M231015155500ExecuteSql::class, $output); + } + } + + /** + * No migrations by the passed namespace and path. + */ + public function testOptionsNamespaceAndPathWithoutMigrations(): void + { + MigrationHelper::useMigrationsNamespace($this->container); + + MigrationHelper::createAndApplyMigration( + $this->container, + 'Create_User', + 'table', + 'user', + ['name:string(50)'], + ); + + $command = $this->createCommand($this->container); + $options = [ + '--namespace' => ['Yiisoft\Db\Migration\Tests\Support\Migrations'], + '-ns' => ['Yiisoft\Db\Migration\Tests\Support\Migrations'], + '--path' => [dirname(__DIR__, 2) . '/Support/Migrations'], + ]; + + foreach ($options as $option => $value) { + $exitCode = $command->execute([$option => $value]); + $output = $command->getDisplay(true); + + $this->assertSame(Command::FAILURE, $exitCode); + $this->assertStringContainsString('[WARNING] No applied migrations found.', $output); + } + } + + /** + * Namespace `Yiisoft\Db\Migration\Tests\Support\MigrationsExtra` matches to two paths, + * all migrations by the passed namespace should be redone. + */ + public function testOptionsNamespaceWithDifferentPaths(): void + { + MigrationHelper::useMigrationsPath($this->container); + + $migrator = $this->container->get(Migrator::class); + $migrator->up(new M231108183919Empty()); + $migrator->up(new M231108183919Empty2()); + + MigrationHelper::useMigrationsNamespace($this->container); + MigrationHelper::createAndApplyMigration( + $this->container, + 'Create_User', + 'table', + 'user', + ['name:string(50)'], + ); + + $command = $this->createCommand($this->container); + $options = [ + '--namespace' => ['Yiisoft\Db\Migration\Tests\Support\MigrationsExtra'], + '-ns' => ['Yiisoft\Db\Migration\Tests\Support\MigrationsExtra'], + ]; + + foreach ($options as $option => $value) { + $exitCode = $command->setInputs(['no'])->execute([$option => $value, '-a' => true]); + $output = $command->getDisplay(true); + + $this->assertSame(Command::SUCCESS, $exitCode); + $this->assertStringContainsString('Total 2 migrations to be redone:', $output); + $this->assertStringContainsString('1. ' . M231108183919Empty2::class, $output); + $this->assertStringContainsString('2. ' . M231108183919Empty::class, $output); + } + } + + /** + * Namespace `Yiisoft\Db\Migration\Tests\Support\MigrationsExtra` matches to two paths, + * but only migrations by the specified path should be redone. + */ + public function testOptionsPathForNamespaceWithDifferentPaths(): void + { + MigrationHelper::useMigrationsPath($this->container); + + $migrator = $this->container->get(Migrator::class); + $migrator->up(new M231108183919Empty()); + $migrator->up(new M231108183919Empty2()); + + MigrationHelper::useMigrationsNamespace($this->container); + MigrationHelper::createAndApplyMigration( + $this->container, + 'Create_User', + 'table', + 'user', + ['name:string(50)'], + ); + + $command = $this->createCommand($this->container); + + $path = dirname(__DIR__, 2) . '/Support/MigrationsExtra'; + $exitCode = $command->setInputs(['no'])->execute(['--path' => [$path], '-a' => true]); + $output = $command->getDisplay(true); + + $this->assertSame(Command::SUCCESS, $exitCode); + $this->assertStringContainsString('Total 1 migration to be redone:', $output); + $this->assertStringContainsString('1. ' . M231108183919Empty::class, $output); + } + public function createCommand(ContainerInterface $container): CommandTester { return CommandHelper::getCommandTester($container, RedoCommand::class);