From 5ae8c9dbeb354f6e24bfe13a6ff881f21b2b0bf3 Mon Sep 17 00:00:00 2001 From: Sergei Tigrov Date: Tue, 7 Nov 2023 17:51:26 +0700 Subject: [PATCH 1/6] Fix and improve tests (#229) --- tests/Common/AbstractMigratorTest.php | 4 ---- tests/Support/Factory/MssqlFactory.php | 11 ++++++++--- tests/Support/Factory/MysqlFactory.php | 11 ++++++++--- tests/Support/Factory/OracleFactory.php | 11 ++++++++--- tests/Support/Factory/SqLiteFactory.php | 3 ++- 5 files changed, 26 insertions(+), 14 deletions(-) diff --git a/tests/Common/AbstractMigratorTest.php b/tests/Common/AbstractMigratorTest.php index 2b1551fd..195e8197 100644 --- a/tests/Common/AbstractMigratorTest.php +++ b/tests/Common/AbstractMigratorTest.php @@ -100,8 +100,6 @@ public function testMaxSqlOutputLength(): void 'Execute SQL: CREATE TABLE person [... hidden] ... Done', $informer->getOutput(), ); - - $migrator->down(new M231015155500ExecuteSql()); } public function testZeroMaxSqlOutputLength(): void @@ -120,7 +118,5 @@ public function testZeroMaxSqlOutputLength(): void 'Execute SQL: [... hidden] ... Done', $informer->getOutput(), ); - - $migrator->down(new M231015155500ExecuteSql()); } } diff --git a/tests/Support/Factory/MssqlFactory.php b/tests/Support/Factory/MssqlFactory.php index 5aa5c23c..1b66d06c 100644 --- a/tests/Support/Factory/MssqlFactory.php +++ b/tests/Support/Factory/MssqlFactory.php @@ -17,6 +17,7 @@ use Yiisoft\Db\Migration\Tests\Support\Helper\ContainerConfig; use Yiisoft\Db\Migration\Tests\Support\Helper\ContainerHelper; +use function array_intersect; use function dirname; final class MssqlFactory @@ -73,12 +74,16 @@ public static function clearDatabase(ContainerInterface $container): void 'test_table', 'target_table', 'new_table', + 'person', + 'book', + 'chapter', ]; + $tables = array_intersect($tables, $db->getSchema()->getTableNames()); + $command = $db->createCommand(); + foreach ($tables as $table) { - if ($db->getTableSchema($table)) { - $db->createCommand()->dropTable($table)->execute(); - } + $command->dropTable($table)->execute(); } $db->close(); diff --git a/tests/Support/Factory/MysqlFactory.php b/tests/Support/Factory/MysqlFactory.php index 90153163..4d52dd64 100644 --- a/tests/Support/Factory/MysqlFactory.php +++ b/tests/Support/Factory/MysqlFactory.php @@ -17,6 +17,7 @@ use Yiisoft\Db\Migration\Tests\Support\Helper\ContainerConfig; use Yiisoft\Db\Migration\Tests\Support\Helper\ContainerHelper; +use function array_intersect; use function dirname; final class MysqlFactory @@ -73,12 +74,16 @@ public static function clearDatabase(ContainerInterface $container): void 'test_table', 'target_table', 'new_table', + 'person', + 'book', + 'chapter', ]; + $tables = array_intersect($tables, $db->getSchema()->getTableNames()); + $command = $db->createCommand(); + foreach ($tables as $table) { - if ($db->getTableSchema($table)) { - $db->createCommand('DROP TABLE IF EXISTS ' . $table . ' CASCADE;')->execute(); - } + $command->setSql('DROP TABLE IF EXISTS ' . $table . ' CASCADE')->execute(); } $db->close(); diff --git a/tests/Support/Factory/OracleFactory.php b/tests/Support/Factory/OracleFactory.php index 1781b6dd..44e25483 100644 --- a/tests/Support/Factory/OracleFactory.php +++ b/tests/Support/Factory/OracleFactory.php @@ -17,6 +17,7 @@ use Yiisoft\Db\Migration\Tests\Support\Helper\ContainerConfig; use Yiisoft\Db\Migration\Tests\Support\Helper\ContainerHelper; +use function array_intersect; use function dirname; final class OracleFactory @@ -73,12 +74,16 @@ public static function clearDatabase(ContainerInterface $container): void 'test_table', 'target_table', 'new_table', + 'PERSON', + 'book', + 'chapter', ]; + $tables = array_intersect($tables, $db->getSchema()->getTableNames()); + $command = $db->createCommand(); + foreach ($tables as $table) { - if ($db->getTableSchema($table) !== null) { - $db->createCommand()->dropTable($table)->execute(); - } + $command->dropTable($table)->execute(); } $db->close(); diff --git a/tests/Support/Factory/SqLiteFactory.php b/tests/Support/Factory/SqLiteFactory.php index 34b4f6fe..7dee09a7 100644 --- a/tests/Support/Factory/SqLiteFactory.php +++ b/tests/Support/Factory/SqLiteFactory.php @@ -55,9 +55,10 @@ static function (string $id) use (&$container, $config): object { public static function clearDatabase(ContainerInterface $container): void { $db = $container->get(SqLiteConnection::class); + $command = $db->createCommand(); foreach ($db->getSchema()->getTableNames() as $tableName) { - $db->createCommand()->dropTable($tableName)->execute(); + $command->dropTable($tableName)->execute(); } $db->close(); From d227ecdd752a6faaf8a2863b2099b4b865408d1a Mon Sep 17 00:00:00 2001 From: Sergei Tigrov Date: Tue, 7 Nov 2023 17:51:48 +0700 Subject: [PATCH 2/6] Output `rawSql()` for `execute()` method (#230) --- src/MigrationBuilder.php | 11 +++++++---- tests/Common/AbstractMigrationBuilderTest.php | 8 ++++++-- tests/Support/Migrations/M231015155500ExecuteSql.php | 2 +- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/MigrationBuilder.php b/src/MigrationBuilder.php index adf16a47..a3aeef4c 100644 --- a/src/MigrationBuilder.php +++ b/src/MigrationBuilder.php @@ -20,6 +20,7 @@ use function rtrim; use function sprintf; use function substr; +use function trim; final class MigrationBuilder extends AbstractMigrationBuilder { @@ -52,13 +53,15 @@ public function getDb(): ConnectionInterface */ public function execute(string $sql, array $params = []): void { - $sqlOutput = $sql; - if ($this->maxSqlOutputLength !== null && $this->maxSqlOutputLength < strlen($sql)) { - $sqlOutput = ltrim(rtrim(substr($sql, 0, $this->maxSqlOutputLength)) . ' [... hidden]'); + $command = $this->db->createCommand($sql)->bindValues($params); + $sqlOutput = trim($command->getRawSql()); + + if ($this->maxSqlOutputLength !== null && $this->maxSqlOutputLength < strlen($sqlOutput)) { + $sqlOutput = ltrim(rtrim(substr($sqlOutput, 0, $this->maxSqlOutputLength)) . ' [... hidden]'); } $time = $this->beginCommand("Execute SQL: $sqlOutput"); - $this->db->createCommand($sql)->bindValues($params)->execute(); + $command->execute(); $this->endCommand($time); } diff --git a/tests/Common/AbstractMigrationBuilderTest.php b/tests/Common/AbstractMigrationBuilderTest.php index ad78163d..a10cefa9 100644 --- a/tests/Common/AbstractMigrationBuilderTest.php +++ b/tests/Common/AbstractMigrationBuilderTest.php @@ -31,10 +31,14 @@ protected function setUp(): void public function testExecute(): void { $this->builder->createTable('test', ['id' => $this->builder->integer()]); - $this->builder->execute('DROP TABLE {{test}}'); + + $sql = 'DROP TABLE {{test}}'; + $this->builder->execute($sql); + + $sqlOutput = $this->db->getQuoter()->quoteSql($sql); $this->assertEmpty($this->db->getTableSchema('test_table')); - $this->assertInformerOutputContains(' > Execute SQL: DROP TABLE {{test}} ... Done in '); + $this->assertInformerOutputContains(" > Execute SQL: $sqlOutput ... Done in "); } public function testInsert(): void diff --git a/tests/Support/Migrations/M231015155500ExecuteSql.php b/tests/Support/Migrations/M231015155500ExecuteSql.php index 25363ee1..8c2ab8b5 100644 --- a/tests/Support/Migrations/M231015155500ExecuteSql.php +++ b/tests/Support/Migrations/M231015155500ExecuteSql.php @@ -17,7 +17,7 @@ public function up(MigrationBuilder $b): void { $b->execute( << Date: Wed, 8 Nov 2023 23:12:18 +0700 Subject: [PATCH 3/6] Revert by path and namespace (#228) * Add options `--namespace` and `--path` for `migrate:down` command * Update limit * Update tests * Update tests * Update tests * Update tests * Update tests * Update tests * Update tests * Add test * Fix test * Apply Rector changes (CI) * Optimizations, suggestions and tests * Add test for filtering migrations without namespace * Clear test --------- Co-authored-by: Tigrov --- composer.json | 5 +- src/Command/DownCommand.php | 52 +++++-- src/Service/MigrationService.php | 110 +++++++++++++- .../Command/AbstractDownCommandTest.php | 140 ++++++++++++++++++ .../Service/AbstractMigrationServiceTest.php | 47 ++++++ .../MigrationsExtra/M231108183919Empty.php | 20 +++ .../MigrationsExtra2/M231108183919Empty2.php | 20 +++ 7 files changed, 380 insertions(+), 14 deletions(-) create mode 100644 tests/Support/MigrationsExtra/M231108183919Empty.php create mode 100644 tests/Support/MigrationsExtra2/M231108183919Empty2.php diff --git a/composer.json b/composer.json index b6d42196..d04bfb15 100644 --- a/composer.json +++ b/composer.json @@ -48,7 +48,10 @@ }, "autoload-dev": { "psr-4": { - "Yiisoft\\Db\\Migration\\Tests\\": "tests" + "Yiisoft\\Db\\Migration\\Tests\\": "tests", + "Yiisoft\\Db\\Migration\\Tests\\Support\\": "tests/Support", + "Yiisoft\\Db\\Migration\\Tests\\ForTest\\": "tests/Support", + "Yiisoft\\Db\\Migration\\Tests\\Support\\MigrationsExtra\\": ["tests/Support/MigrationsExtra", "tests/Support/MigrationsExtra2"] } }, "extra": { diff --git a/src/Command/DownCommand.php b/src/Command/DownCommand.php index 29118c8e..1e9e15e7 100644 --- a/src/Command/DownCommand.php +++ b/src/Command/DownCommand.php @@ -18,6 +18,7 @@ use Yiisoft\Db\Migration\Service\MigrationService; use function array_keys; +use function array_slice; use function count; /** @@ -26,9 +27,15 @@ * For example: * * ```shell - * ./yii migrate:down # revert the last migration - * ./yii migrate:down --limit=3 # revert the last 3 migrations - * ./yii migrate:down --all # revert all migrations + * ./yii migrate:down # revert the last migration + * ./yii migrate:down --limit=3 # revert last 3 migrations + * ./yii migrate:down --all # revert all migrations + * ./yii migrate:down --path=@vendor/yiisoft/rbac-db/migrations # revert the last migration from the directory + * ./yii migrate:down --namespace=Yiisoft\\Rbac\\Db\\Migrations # revert the last migration from the namespace + * + * # revert migrations from multiple directories and namespaces + * ./yii migrate:down --path=@vendor/yiisoft/rbac-db/migrations --path=@vendor/yiisoft/cache-db/migrations + * ./yii migrate:down --namespace=Yiisoft\\Rbac\\Db\\Migrations --namespace=Yiisoft\\Cache\\Db\\Migrations * ``` */ #[AsCommand('migrate:down', 'Reverts the specified number of latest migrations.')] @@ -46,7 +53,9 @@ protected function configure(): void { $this ->addOption('limit', 'l', InputOption::VALUE_REQUIRED, 'Number of migrations to revert.', 1) - ->addOption('all', 'a', InputOption::VALUE_NONE, 'Revert all migrations.'); + ->addOption('all', 'a', InputOption::VALUE_NONE, 'Revert all migrations.') + ->addOption('path', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Path to migrations to revert.') + ->addOption('namespace', 'ns', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Namespace of migrations to revert.'); } protected function execute(InputInterface $input, OutputInterface $output): int @@ -69,16 +78,37 @@ 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)) { - $output->writeln(" >>> Apply at least one migration first.\n"); - $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); + + if (empty($migrations)) { + $output->writeln(" >>> Apply at least one migration first.\n"); + $io->warning('No migration has been done before.'); - $migrations = array_keys($migrations); + return Command::FAILURE; + } + + $migrations = array_keys($migrations); + } $n = count($migrations); $migrationWord = $n === 1 ? 'migration' : 'migrations'; diff --git a/src/Service/MigrationService.php b/src/Service/MigrationService.php index f62b824d..6b96b7a4 100644 --- a/src/Service/MigrationService.php +++ b/src/Service/MigrationService.php @@ -17,19 +17,29 @@ use Yiisoft\Db\Migration\RevertibleMigrationInterface; use function array_map; +use function array_unique; use function array_values; use function closedir; use function dirname; use function gmdate; +use function in_array; use function is_dir; use function is_file; +use function krsort; use function ksort; use function opendir; use function preg_match; use function preg_replace; use function readdir; +use function realpath; +use function reset; use function str_contains; use function str_replace; +use function str_starts_with; +use function strlen; +use function strrchr; +use function strrpos; +use function substr; use function trim; use function ucwords; @@ -102,7 +112,7 @@ public function before(string $commandName): int /** * Returns the migrations that are not applied. * - * @return array List of new migrations. + * @return string[] List of new migrations. * * @psalm-return list */ @@ -353,6 +363,66 @@ public function findMigrationPath(): string : $this->getNamespacePath($this->createNamespace); } + /** + * Filters migrations by namespaces and paths. + * + * @param string[] $classes Migration classes to be filtered. + * @param string[] $namespaces Namespaces to filter by. + * @param string[] $paths Paths to filter by. + * + * @return string[] Filtered migration classes. + * + * @psalm-param list $classes + * + * @psalm-return list + */ + public function filterMigrations(array $classes, array $namespaces = [], array $paths = []): array + { + $result = []; + $pathNamespaces = []; + + foreach ($paths as $path) { + $pathNamespaceList = $this->getNamespacesFromPath($path); + + if (!empty($pathNamespaceList)) { + $pathNamespaces[$path] = $pathNamespaceList; + } + } + + $namespaces = array_map(static fn ($namespace) => trim($namespace, '\\'), $namespaces); + $namespaces = array_unique($namespaces); + + foreach ($classes as $class) { + $classNamespace = substr($class, 0, strrpos($class, '\\') ?: 0); + + if ($classNamespace === '') { + continue; + } + + if (in_array($classNamespace, $namespaces, true)) { + $result[] = $class; + continue; + } + + foreach ($pathNamespaces as $path => $pathNamespaceList) { + /** @psalm-suppress RedundantCondition */ + if (!in_array($classNamespace, $pathNamespaceList, true)) { + continue; + } + + $className = substr(strrchr($class, '\\'), 1); + $file = $path . DIRECTORY_SEPARATOR . $className . '.php'; + + if (is_file($file)) { + $result[] = $class; + break; + } + } + } + + return $result; + } + /** * Returns the file path matching the give namespace. * @@ -374,7 +444,7 @@ private function getPathFromNamespace(string $path): string /** @psalm-suppress UnresolvableInclude */ $map = require $this->getVendorDir() . '/composer/autoload_psr4.php'; - /** @psalm-var array> $map */ + /** @psalm-var array> $map */ foreach ($map as $namespace => $directories) { foreach ($directories as $directory) { $namespacesPath[str_replace('\\', '/', trim($namespace, '\\'))] = $directory; @@ -384,6 +454,42 @@ private function getPathFromNamespace(string $path): string return (new Aliases($namespacesPath))->get($path); } + /** + * Returns the namespaces matching the give file path. + * + * @param string $path File path. + * + * @return string[] Namespaces. + */ + private function getNamespacesFromPath(string $path): array + { + $namespaces = []; + $path = realpath($this->aliases->get($path)) . DIRECTORY_SEPARATOR; + /** @psalm-suppress UnresolvableInclude */ + $map = require $this->getVendorDir() . '/composer/autoload_psr4.php'; + + /** @psalm-var array> $map */ + foreach ($map as $namespace => $directories) { + foreach ($directories as $directory) { + $directory = realpath($directory) . DIRECTORY_SEPARATOR; + + if (str_starts_with($path, $directory)) { + $length = strlen($directory); + $pathNamespace = $namespace . str_replace('/', '\\', substr($path, $length)); + $namespaces[$length][$namespace] = rtrim($pathNamespace, '\\'); + } + } + } + + if (empty($namespaces)) { + return []; + } + + krsort($namespaces); + + return array_values(reset($namespaces)); + } + private function getVendorDir(): string { $class = new ReflectionClass(ClassLoader::class); diff --git a/tests/Common/Command/AbstractDownCommandTest.php b/tests/Common/Command/AbstractDownCommandTest.php index b8fb1477..2fffbd27 100644 --- a/tests/Common/Command/AbstractDownCommandTest.php +++ b/tests/Common/Command/AbstractDownCommandTest.php @@ -17,8 +17,13 @@ use Yiisoft\Db\Migration\Tests\Support\AssertTrait; use Yiisoft\Db\Migration\Tests\Support\Helper\CommandHelper; use Yiisoft\Db\Migration\Tests\Support\Helper\MigrationHelper; +use Yiisoft\Db\Migration\Tests\Support\Migrations\M231015155500ExecuteSql; +use Yiisoft\Db\Migration\Tests\Support\MigrationsExtra\M231108183919Empty; +use Yiisoft\Db\Migration\Tests\Support\MigrationsExtra\M231108183919Empty2; use Yiisoft\Db\Migration\Tests\Support\Stub\StubMigration; +use function dirname; + abstract class AbstractDownCommandTest extends TestCase { use AssertTrait; @@ -341,6 +346,141 @@ public function testNotReverted(): void $this->assertStringContainsString('[ERROR] Not reverted.', $output); } + 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 reverted:', $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 reverted. + */ + 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 reverted:', $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 reverted. + */ + 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 reverted:', $output); + $this->assertStringContainsString('1. ' . M231108183919Empty::class, $output); + } + public function createCommand(ContainerInterface $container): CommandTester { return CommandHelper::getCommandTester($container, DownCommand::class); diff --git a/tests/Common/Service/AbstractMigrationServiceTest.php b/tests/Common/Service/AbstractMigrationServiceTest.php index f2662433..082ec9ee 100644 --- a/tests/Common/Service/AbstractMigrationServiceTest.php +++ b/tests/Common/Service/AbstractMigrationServiceTest.php @@ -10,6 +10,8 @@ use Yiisoft\Db\Migration\Service\MigrationService; use Yiisoft\Db\Migration\Tests\Support\Helper\MigrationHelper; +use function dirname; + abstract class AbstractMigrationServiceTest extends TestCase { protected ContainerInterface $container; @@ -53,4 +55,49 @@ public function testGetNewMigrationsWithNotExistNamespace(): void $this->assertSame([$className], $migrations); } + + public function testGetNamespacesFromPathForNoHavingNamespacePath(): void + { + $migrationService = $this->container->get(MigrationService::class); + + $getNamespaceFromPath = new \ReflectionMethod($migrationService, 'getNamespacesFromPath'); + $getNamespaceFromPath->setAccessible(true); + + // No having namespace path + $path = dirname(__DIR__, 3) . '/config'; + + $this->assertSame([], $getNamespaceFromPath->invoke($migrationService, $path)); + } + + /** + * Test MigrationService::getNamespacesFromPath() returns namespaces corresponding to the longest subdirectory of a path. + * One path can match to several namespaces. + */ + public function testGetNamespacesFromPathForLongestPath(): void + { + $migrationService = $this->container->get(MigrationService::class); + + $getNamespaceFromPath = new \ReflectionMethod($migrationService, 'getNamespacesFromPath'); + $getNamespaceFromPath->setAccessible(true); + + /** + * Path corresponding to three namespaces: + * `Yiisoft\\Db\\Migration\\Tests\\` + * `Yiisoft\\Db\\Migration\\Tests\\Support\\` + * `Yiisoft\\Db\\Migration\\Tests\\ForTest\\` + */ + $path = dirname(__DIR__, 2) . '/Support/Migrations'; + + $this->assertSame( + ['Yiisoft\Db\Migration\Tests\Support\Migrations', 'Yiisoft\Db\Migration\Tests\ForTest\Migrations'], + $getNamespaceFromPath->invoke($migrationService, $path), + ); + } + + public function testFilterMigrationsWithoutNamespace(): void + { + $migrationService = $this->container->get(MigrationService::class); + + $this->assertSame([], $migrationService->filterMigrations(['ClassNameWithoutNamespace'])); + } } diff --git a/tests/Support/MigrationsExtra/M231108183919Empty.php b/tests/Support/MigrationsExtra/M231108183919Empty.php new file mode 100644 index 00000000..4f4237ec --- /dev/null +++ b/tests/Support/MigrationsExtra/M231108183919Empty.php @@ -0,0 +1,20 @@ + Date: Thu, 9 Nov 2023 18:02:10 +0700 Subject: [PATCH 4/6] Add set prefix to MigrationService setters (#231) --- src/Command/CreateCommand.php | 6 +++--- src/Command/NewCommand.php | 4 ++-- src/Command/UpdateCommand.php | 4 ++-- src/Service/MigrationService.php | 18 +++++++++--------- .../Command/AbstractCreateCommandTest.php | 8 ++++---- .../Command/AbstractUpdateCommandTest.php | 4 ++-- .../Service/AbstractMigrationServiceTest.php | 2 +- tests/Support/Helper/MigrationHelper.php | 16 ++++++++-------- 8 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/Command/CreateCommand.php b/src/Command/CreateCommand.php index 97458568..fe87ac38 100644 --- a/src/Command/CreateCommand.php +++ b/src/Command/CreateCommand.php @@ -59,7 +59,7 @@ * ./yii migrate:create post --command=table --path=@root/migrations/blog * ``` * - * In case {@see createPath} is not set and no namespace is provided, {@see createNamespace} will be used. + * In case {@see $createPath} is not set and no namespace is provided, {@see $createNamespace} will be used. */ #[AsCommand('migrate:create', 'Creates a new migration.')] final class CreateCommand extends Command @@ -101,8 +101,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int $namespace = $input->getOption('namespace'); if ($path !== null || $namespace !== null) { - $this->migrationService->createPath((string) $path); - $this->migrationService->createNamespace((string) $namespace); + $this->migrationService->setCreatePath((string) $path); + $this->migrationService->setCreateNamespace((string) $namespace); } else { $namespace = $this->migrationService->getCreateNamespace(); } diff --git a/src/Command/NewCommand.php b/src/Command/NewCommand.php index 7c26fba4..e2e1a860 100644 --- a/src/Command/NewCommand.php +++ b/src/Command/NewCommand.php @@ -66,8 +66,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int $namespaces = $input->getOption('namespace'); if (!empty($paths) || !empty($namespaces)) { - $this->migrationService->updatePaths($paths); - $this->migrationService->updateNamespaces($namespaces); + $this->migrationService->setUpdatePaths($paths); + $this->migrationService->setUpdateNamespaces($namespaces); } $this->migrationService->before(self::getDefaultName() ?? ''); diff --git a/src/Command/UpdateCommand.php b/src/Command/UpdateCommand.php index f68be8f6..ffbdbc14 100644 --- a/src/Command/UpdateCommand.php +++ b/src/Command/UpdateCommand.php @@ -70,8 +70,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int $namespaces = $input->getOption('namespace'); if (!empty($paths) || !empty($namespaces)) { - $this->migrationService->updatePaths($paths); - $this->migrationService->updateNamespaces($namespaces); + $this->migrationService->setUpdatePaths($paths); + $this->migrationService->setUpdateNamespaces($namespaces); } if ($this->migrationService->before(self::getDefaultName() ?? '') === Command::INVALID) { diff --git a/src/Service/MigrationService.php b/src/Service/MigrationService.php index 6b96b7a4..ba3221c3 100644 --- a/src/Service/MigrationService.php +++ b/src/Service/MigrationService.php @@ -70,8 +70,8 @@ public function setIO(?SymfonyStyle $io): void /** * This method is invoked right before an action is to be executed (after all possible filters.) * - * It checks the existence of the {@see createPath}, {@see updatePaths}, {@see createNamespace}, - * {@see updateNamespaces}. + * It checks the existence of the {@see $createPath}, {@see $updatePaths}, {@see $createNamespace}, + * {@see $updateNamespaces}. * * @return int Whether the action should continue to be executed. */ @@ -182,7 +182,7 @@ public function getNewMigrations(): array * * @psalm-param string[] $value */ - public function updateNamespaces(array $value): void + public function setUpdateNamespaces(array $value): void { $this->updateNamespaces = $value; } @@ -193,12 +193,12 @@ public function updateNamespaces(array $value): void * This can be either a [path alias](guide:concept-aliases) or a directory path. * * Migration classes located at this path should be declared without a namespace. - * Use {@see createNamespace} property in case you are using namespaced migrations. + * Use {@see $createNamespace} property in case you are using namespaced migrations. * - * If you have set up {@see createNamespace}, you may set this field to `null` in order to disable usage of migrations + * If you have set up {@see $createNamespace}, you may set this field to `null` in order to disable usage of migrations * that are not namespaced. * - * In general, to load migrations from different locations, {@see createNamespace} is the preferable solution as the + * In general, to load migrations from different locations, {@see $createNamespace} is the preferable solution as the * migration name contains the origin of the migration in the history, which is not the case when using multiple * migration paths. * @@ -207,7 +207,7 @@ public function updateNamespaces(array $value): void * * @psalm-param string[] $value */ - public function updatePaths(array $value): void + public function setUpdatePaths(array $value): void { $this->updatePaths = $value; } @@ -318,12 +318,12 @@ public function makeRevertibleMigrations(array $classes): array ); } - public function createNamespace(string $value): void + public function setCreateNamespace(string $value): void { $this->createNamespace = $value; } - public function createPath(string $value): void + public function setCreatePath(string $value): void { $this->createPath = $value; } diff --git a/tests/Common/Command/AbstractCreateCommandTest.php b/tests/Common/Command/AbstractCreateCommandTest.php index caaec379..b306a5d9 100644 --- a/tests/Common/Command/AbstractCreateCommandTest.php +++ b/tests/Common/Command/AbstractCreateCommandTest.php @@ -996,7 +996,7 @@ public function testIncorrectCreatePath(): void { MigrationHelper::useMigrationsPath($this->container); - $this->container->get(MigrationService::class)->createPath(__DIR__ . '/not-exists'); + $this->container->get(MigrationService::class)->setCreatePath(__DIR__ . '/not-exists'); $command = $this->createCommand($this->container); $command->setInputs(['yes']); @@ -1012,7 +1012,7 @@ public function testWithoutCreatePath(): void { MigrationHelper::useMigrationsPath($this->container); - $this->container->get(MigrationService::class)->createPath(''); + $this->container->get(MigrationService::class)->setCreatePath(''); $command = $this->createCommand($this->container); $command->setInputs(['yes']); @@ -1032,7 +1032,7 @@ public function testIncorrectCreateNamespace(): void MigrationHelper::useMigrationsNamespace($this->container); $this->container->get(MigrationService::class) - ->createNamespace('Yiisoft\\Db\\Migration\\TestsRuntime\\NotExists'); + ->setCreateNamespace('Yiisoft\\Db\\Migration\\TestsRuntime\\NotExists'); $command = $this->createCommand($this->container); $command->setInputs(['yes']); @@ -1048,7 +1048,7 @@ public function testWithoutCreateNamespace(): void { MigrationHelper::useMigrationsNamespace($this->container); - $this->container->get(MigrationService::class)->createNamespace(''); + $this->container->get(MigrationService::class)->setCreateNamespace(''); $command = $this->createCommand($this->container); $command->setInputs(['yes']); diff --git a/tests/Common/Command/AbstractUpdateCommandTest.php b/tests/Common/Command/AbstractUpdateCommandTest.php index 5e912d61..7bcdfbb3 100644 --- a/tests/Common/Command/AbstractUpdateCommandTest.php +++ b/tests/Common/Command/AbstractUpdateCommandTest.php @@ -271,7 +271,7 @@ public function testWithoutUpdatePath(): void { MigrationHelper::useMigrationsPath($this->container); - $this->container->get(MigrationService::class)->updatePaths([]); + $this->container->get(MigrationService::class)->setUpdatePaths([]); $command = $this->createCommand($this->container); $command->setInputs(['yes']); @@ -290,7 +290,7 @@ public function testWithoutUpdateNamespaces(): void { MigrationHelper::useMigrationsNamespace($this->container); - $this->container->get(MigrationService::class)->updateNamespaces([]); + $this->container->get(MigrationService::class)->setUpdateNamespaces([]); $command = $this->createCommand($this->container); $command->setInputs(['yes']); diff --git a/tests/Common/Service/AbstractMigrationServiceTest.php b/tests/Common/Service/AbstractMigrationServiceTest.php index 082ec9ee..e0472d94 100644 --- a/tests/Common/Service/AbstractMigrationServiceTest.php +++ b/tests/Common/Service/AbstractMigrationServiceTest.php @@ -46,7 +46,7 @@ public function testGetNewMigrationsWithNotExistNamespace(): void $service = $this->container->get(MigrationService::class); - $service->updateNamespaces([ + $service->setUpdateNamespaces([ MigrationHelper::NAMESPACE, 'Yiisoft\\Db\\Migration\\TestsRuntime\\NotExists', ]); diff --git a/tests/Support/Helper/MigrationHelper.php b/tests/Support/Helper/MigrationHelper.php index 6dfea2e6..a29ae50b 100644 --- a/tests/Support/Helper/MigrationHelper.php +++ b/tests/Support/Helper/MigrationHelper.php @@ -26,8 +26,8 @@ public static function useMigrationsPath(ContainerInterface $container): string { $service = $container->get(MigrationService::class); - $service->createPath(self::PATH_ALIAS); - $service->updatePaths([self::PATH_ALIAS]); + $service->setCreatePath(self::PATH_ALIAS); + $service->setUpdatePaths([self::PATH_ALIAS]); self::preparePaths($container); @@ -41,8 +41,8 @@ public static function useMigrationsNamespace(ContainerInterface $container): st { $service = $container->get(MigrationService::class); - $service->createNamespace(self::NAMESPACE); - $service->updateNamespaces([self::NAMESPACE]); + $service->setCreateNamespace(self::NAMESPACE); + $service->setUpdateNamespaces([self::NAMESPACE]); self::preparePaths($container); @@ -136,9 +136,9 @@ public static function resetPathAndNamespace(ContainerInterface $container): voi { $service = $container->get(MigrationService::class); - $service->createPath(''); - $service->updatePaths([]); - $service->createNamespace(''); - $service->updateNamespaces([]); + $service->setCreatePath(''); + $service->setUpdatePaths([]); + $service->setCreateNamespace(''); + $service->setUpdateNamespaces([]); } } From ad391f4e775b9fe4ead36ec87e488f5f2dd0a2fb Mon Sep 17 00:00:00 2001 From: Sergei Tigrov Date: Fri, 10 Nov 2023 13:18:28 +0700 Subject: [PATCH 5/6] Fix config for added set prefixes to `MigrationService` setters (#233) Co-authored-by: Sergei Predvoditelev --- .github/workflows/build.yml | 8 ++++---- bin/MigrationContainer.php | 8 ++++---- config/di-console.php | 8 ++++---- docs/en/usage-with-symfony-console.md | 8 ++++---- tests/Migration/ConfigTest.php | 2 +- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c51db46c..ca096372 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -38,9 +38,9 @@ jobs: - ubuntu-latest php: - - 8.0 - - 8.1 - - 8.2 + - '8.0' + - '8.1' + - '8.2' steps: - name: Checkout. @@ -61,7 +61,7 @@ jobs: run: composer require yiisoft/db-sqlite:^1.0 --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi - name: Run tests with phpunit. - run: vendor/bin/phpunit --testsuite=YiiDbMigration --coverage-clover=coverage.xml --colors=always + run: vendor/bin/phpunit --testsuite=Migration --coverage-clover=coverage.xml --colors=always - name: Upload coverage to Codecov. if: matrix.php == '8.1' diff --git a/bin/MigrationContainer.php b/bin/MigrationContainer.php index ccdfedfe..b49955f4 100644 --- a/bin/MigrationContainer.php +++ b/bin/MigrationContainer.php @@ -29,10 +29,10 @@ public static function definitions(): array ], MigrationService::class => [ 'class' => MigrationService::class, - 'createNamespace()' => [''], - 'createPath()' => [''], - 'updateNamespaces()' => [[]], - 'updatePaths()' => [[]], + 'setCreateNamespace()' => [''], + 'setCreatePath()' => [''], + 'setUpdateNamespaces()' => [[]], + 'setUpdatePaths()' => [[]], ], Migrator::class => [ '__constructor()' => [ diff --git a/config/di-console.php b/config/di-console.php index fda44604..06d5345c 100644 --- a/config/di-console.php +++ b/config/di-console.php @@ -11,10 +11,10 @@ return [ MigrationService::class => [ 'class' => MigrationService::class, - 'createNamespace()' => [$params['yiisoft/db-migration']['createNamespace']], - 'updateNamespaces()' => [$params['yiisoft/db-migration']['updateNamespaces']], - 'createPath()' => [$params['yiisoft/db-migration']['createPath']], - 'updatePaths()' => [$params['yiisoft/db-migration']['updatePaths']], + 'setCreateNamespace()' => [$params['yiisoft/db-migration']['createNamespace']], + 'setUpdateNamespaces()' => [$params['yiisoft/db-migration']['updateNamespaces']], + 'setCreatePath()' => [$params['yiisoft/db-migration']['createPath']], + 'setUpdatePaths()' => [$params['yiisoft/db-migration']['updatePaths']], ], MigrationInformerInterface::class => ConsoleMigrationInformer::class, diff --git a/docs/en/usage-with-symfony-console.md b/docs/en/usage-with-symfony-console.md index 74f5b7d7..39e610e2 100644 --- a/docs/en/usage-with-symfony-console.md +++ b/docs/en/usage-with-symfony-console.md @@ -56,10 +56,10 @@ final class MigrationContainer ], MigrationService::class => [ 'class' => MigrationService::class, - 'createNamespace()' => [''], - 'createPath()' => [''], - 'updateNamespaces()' => [['Yii\\User\\Framework\\Migration']], - 'updatePaths()' => [[]], + 'serCreateNamespace()' => [''], + 'setCreatePath()' => [''], + 'setUpdateNamespaces()' => [['Yii\\User\\Framework\\Migration']], + 'setUpdatePaths()' => [[]], ], MigrationInformerInterface::class => ConsoleMigrationInformer::class, ]; diff --git a/tests/Migration/ConfigTest.php b/tests/Migration/ConfigTest.php index fbbdb62a..12f60977 100644 --- a/tests/Migration/ConfigTest.php +++ b/tests/Migration/ConfigTest.php @@ -45,7 +45,7 @@ public function testBase(): void $this->assertInstanceOf(UpdateCommand::class, $container->get(UpdateCommand::class)); // Informer - $this->assertInstanceOf(NullMigrationInformer::class, $container->get(MigrationInformerInterface::class)); + $this->assertInstanceOf(ConsoleMigrationInformer::class, $container->get(MigrationInformerInterface::class)); $this->assertInstanceOf(NullMigrationInformer::class, $container->get(NullMigrationInformer::class)); $this->assertInstanceOf(ConsoleMigrationInformer::class, $container->get(ConsoleMigrationInformer::class)); From a9bfbf05c30918d90608f66321af7626d1abe153 Mon Sep 17 00:00:00 2001 From: Sergei Tigrov Date: Fri, 10 Nov 2023 13:30:22 +0700 Subject: [PATCH 6/6] Fix Oracle tests running locally (#232) --- tests/Support/Factory/OracleFactory.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Support/Factory/OracleFactory.php b/tests/Support/Factory/OracleFactory.php index 44e25483..1b40a18c 100644 --- a/tests/Support/Factory/OracleFactory.php +++ b/tests/Support/Factory/OracleFactory.php @@ -40,7 +40,7 @@ static function (string $id) use (&$container, $config): object { return match ($id) { ConnectionInterface::class => new OracleConnection( new OracleDriver( - 'oci:dbname=localhost:1521;charset=AL32UTF8', + 'oci:dbname=localhost:1521/XE;charset=AL32UTF8', 'system', 'root', ), @@ -83,7 +83,7 @@ public static function clearDatabase(ContainerInterface $container): void $command = $db->createCommand(); foreach ($tables as $table) { - $command->dropTable($table)->execute(); + $command->setSql('DROP TABLE "' . $table . '" CASCADE CONSTRAINTS')->execute(); } $db->close();