From 63823ad0e3c7d6eb27efa5f3f4da4d453416aa06 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Tue, 19 May 2026 11:49:22 +0700 Subject: [PATCH 1/5] Fix migration namespaces and paths --- src/Command/CreateCommand.php | 11 ++- src/Service/MigrationService.php | 79 ++++++++++++------- .../Command/AbstractCreateCommandTest.php | 2 +- .../Service/AbstractMigrationServiceTest.php | 60 +++++++------- 4 files changed, 88 insertions(+), 64 deletions(-) diff --git a/src/Command/CreateCommand.php b/src/Command/CreateCommand.php index 1ea1b4ad..1e57e263 100644 --- a/src/Command/CreateCommand.php +++ b/src/Command/CreateCommand.php @@ -4,6 +4,7 @@ namespace Yiisoft\Db\Migration\Command; +use LogicException; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; @@ -150,10 +151,16 @@ protected function execute(InputInterface $input, OutputInterface $output): int return Command::INVALID; } - $migrationPath = $this->migrationService->findMigrationPath(); + try { + $migrationPath = $this->migrationService->findMigrationPath(); + } catch (LogicException $e) { + $io->error($e->getMessage()); + + return Command::INVALID; + } if (!is_dir($migrationPath)) { - $io->error("Invalid path directory $migrationPath"); + $io->error("Invalid path directory \"$migrationPath\""); return Command::INVALID; } diff --git a/src/Service/MigrationService.php b/src/Service/MigrationService.php index 52383451..a62e3ef4 100644 --- a/src/Service/MigrationService.php +++ b/src/Service/MigrationService.php @@ -19,6 +19,7 @@ use function array_map; use function array_unique; use function array_values; +use function class_exists; use function closedir; use function dirname; use function gmdate; @@ -33,6 +34,7 @@ use function readdir; use function realpath; use function reset; +use function rtrim; use function str_contains; use function str_replace; use function str_starts_with; @@ -131,11 +133,7 @@ public function getNewMigrations(): array $migrations = []; $migrationPaths = $this->findSourcePaths(); - foreach ($migrationPaths as [$sourcePath, $namespace]) { - if (!is_dir($sourcePath)) { - continue; - } - + foreach ($migrationPaths as $sourcePath => $namespace) { /** @var resource $handle */ $handle = opendir($sourcePath); while (($file = readdir($handle)) !== false) { @@ -145,18 +143,23 @@ public function getNewMigrations(): array $path = $sourcePath . DIRECTORY_SEPARATOR . $file; - if (is_file($path) && preg_match('/^(M(\d{12}).*)\.php$/s', $file, $matches)) { - [, $class, $time] = $matches; + if (!is_file($path)) { + continue; + } - if (!empty($namespace)) { - $class = $namespace . '\\' . $class; - } + if (preg_match('/^(M(\d{12}).*)\.php$/s', $file, $matches) !== 1) { + continue; + } - /** @psalm-var class-string $class */ + [, $class, $time] = $matches; - if (!isset($applied[$class])) { - $migrations[$time . '\\' . $class] = $class; - } + if (class_exists($namespace . '\\' . $class)) { + $class = $namespace . '\\' . $class; + } + + /** @psalm-var class-string $class */ + if (!isset($applied[$class])) { + $migrations[$time . '\\' . $class] = $class; } } closedir($handle); @@ -389,11 +392,13 @@ private function makeMigrationInstance(string $class): object if (!str_contains($class, '\\')) { $isIncluded = false; - $sourcePaths = $this->newMigrationPath !== '' - ? [$this->newMigrationPath, ...$this->sourcePaths] - : $this->sourcePaths; + $sourcePaths = $this->findSourcePaths(); + + foreach ($sourcePaths as $path => $namespace) { + if (class_exists($namespace . '\\' . $class)) { + continue; + } - foreach ($sourcePaths as $path) { $file = $path . DIRECTORY_SEPARATOR . $class . '.php'; if (is_file($file)) { @@ -418,26 +423,38 @@ private function makeMigrationInstance(string $class): object /** * Returns the migration paths with namespaces if they are specified. * - * @return array + * @return array */ private function findSourcePaths(): array { $paths = []; if ($this->newMigrationPath !== '') { - $paths[] = [$this->newMigrationPath, '']; + if (!is_dir($this->newMigrationPath)) { + throw new LogicException("Invalid path directory \"$this->newMigrationPath\""); + } + + $newMigrationPath = rtrim(str_replace('\\', '/', $this->newMigrationPath), '/'); + $newMigrationNamespace = $this->getNamespacesFromPath($newMigrationPath)[0] ?? ''; + $paths[$newMigrationPath] = $newMigrationNamespace; } elseif ($this->newMigrationNamespace !== '') { $newMigrationPath = $this->getNamespacePath($this->newMigrationNamespace); - $paths[] = [$newMigrationPath, $this->newMigrationNamespace]; + $paths[$newMigrationPath] = $this->newMigrationNamespace; } - foreach ($this->sourcePaths as $sourcePaths) { - $paths[] = [$sourcePaths, '']; + foreach ($this->sourcePaths as $sourcePath) { + if (!is_dir($sourcePath)) { + throw new LogicException("Invalid path directory \"$sourcePath\""); + } + + $sourcePath = rtrim(str_replace('\\', '/', $sourcePath), '/'); + $sourceNamespace = $this->getNamespacesFromPath($sourcePath)[0] ?? ''; + $paths[$sourcePath] = $sourceNamespace; } - foreach ($this->sourceNamespaces as $namespace) { - $sourcePath = $this->getNamespacePath($namespace); - $paths[] = [$sourcePath, $namespace]; + foreach ($this->sourceNamespaces as $sourceNamespace) { + $sourcePath = $this->getNamespacePath($sourceNamespace); + $paths[$sourcePath] = $sourceNamespace; } return $paths; @@ -448,6 +465,8 @@ private function findSourcePaths(): array * * @param string $namespace Namespace. * + * @throws LogicException If the namespace is invalid. + * * @return string File path. */ private function getNamespacePath(string $namespace): string @@ -462,11 +481,15 @@ private function getNamespacePath(string $namespace): string if (str_starts_with($namespace, trim($mapNamespace, '\\'))) { /** @var string $mapDirectory */ $mapDirectory = reset($mapDirectories); - return $mapDirectory . '/' . str_replace('\\', '/', substr($namespace, strlen($mapNamespace))); + $path = $mapDirectory . '/' . str_replace('\\', '/', substr($namespace, strlen($mapNamespace))); + + if (is_dir($path)) { + return rtrim($path, '/'); + } } } - throw new LogicException("Invalid namespace: \"$namespace\"."); + throw new LogicException("Invalid namespace \"$namespace\""); } /** diff --git a/tests/Common/Command/AbstractCreateCommandTest.php b/tests/Common/Command/AbstractCreateCommandTest.php index 5a4ac47b..b6d12d24 100644 --- a/tests/Common/Command/AbstractCreateCommandTest.php +++ b/tests/Common/Command/AbstractCreateCommandTest.php @@ -1025,7 +1025,7 @@ public function testIncorrectNewMigrationNamespace(): void $output = preg_replace('/(\R|\s)+/', ' ', $command->getDisplay(true)); $this->assertSame(Command::INVALID, $exitCode); - $this->assertStringContainsString('Invalid path directory', $output); + $this->assertStringContainsString('Invalid namespace "Yiisoft\Db\Migration\TestsRuntime\NotExists"', $output); } public function testWithoutNewMigrationNamespace(): void diff --git a/tests/Common/Service/AbstractMigrationServiceTest.php b/tests/Common/Service/AbstractMigrationServiceTest.php index e2e53f88..ce74fac9 100644 --- a/tests/Common/Service/AbstractMigrationServiceTest.php +++ b/tests/Common/Service/AbstractMigrationServiceTest.php @@ -4,11 +4,11 @@ namespace Yiisoft\Db\Migration\Tests\Common\Service; +use LogicException; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Psr\Container\ContainerInterface; use ReflectionMethod; -use Yiisoft\Db\Migration\Migrator; use Yiisoft\Db\Migration\Service\MigrationService; use Yiisoft\Db\Migration\Tests\Support\Helper\MigrationHelper; @@ -28,24 +28,6 @@ public function testVersion(): void public function testGetNewMigrationsWithNotExistNamespace(): void { MigrationHelper::useMigrationsNamespace($this->container); - - $className = MigrationHelper::createMigration( - $this->container, - 'Create_Post', - 'table', - 'post', - ['name:string(50)'], - ); - $this->container->get(Migrator::class)->up(new $className()); - - $className = MigrationHelper::createMigration( - $this->container, - 'Create_User', - 'table', - 'user', - ['name:string(32)'], - ); - $service = $this->container->get(MigrationService::class); $service->setSourceNamespaces([ @@ -53,9 +35,10 @@ public function testGetNewMigrationsWithNotExistNamespace(): void 'Yiisoft\\Db\\Migration\\TestsRuntime\\NotExists', ]); - $migrations = $service->getNewMigrations(); + $this->expectException(LogicException::class); + $this->expectExceptionMessage('Invalid namespace "Yiisoft\Db\Migration\TestsRuntime\NotExists"'); - $this->assertSame([$className], $migrations); + $service->getNewMigrations(); } public static function getNewMigrationsDataProvider(): array @@ -67,17 +50,26 @@ public static function getNewMigrationsDataProvider(): array 'non exists newMigrationNamespace' => [ 'expected' => [], 'newMigrationNamespace' => 'Yiisoft\Db\Migration\TestsRuntime\NotExists', + 'newMigrationPath' => '', + 'sourceNamespaces' => [], + 'sourcePaths' => [], + 'errorMessage' => 'Invalid namespace "Yiisoft\Db\Migration\TestsRuntime\NotExists"', ], 'non exists newMigrationPath' => [ 'expected' => [], 'newMigrationNamespace' => '', 'newMigrationPath' => dirname(__DIR__, 2) . '/non-exists-directory', + 'sourceNamespaces' => [], + 'sourcePaths' => [], + 'errorMessage' => 'Invalid path directory "' . dirname(__DIR__, 2) . '/non-exists-directory"', ], 'non exists sourceNamespaces' => [ 'expected' => [], 'newMigrationNamespace' => '', 'newMigrationPath' => '', 'sourceNamespaces' => ['Yiisoft\Db\Migration\TestsRuntime\NotExists'], + 'sourcePaths' => [], + 'errorMessage' => 'Invalid namespace "Yiisoft\Db\Migration\TestsRuntime\NotExists"', ], 'non exists sourcePaths' => [ 'expected' => [], @@ -85,13 +77,14 @@ public static function getNewMigrationsDataProvider(): array 'newMigrationPath' => '', 'sourceNamespaces' => [], 'sourcePaths' => [dirname(__DIR__, 2) . '/non-exists-directory'], + 'errorMessage' => 'Invalid path directory "' . dirname(__DIR__, 2) . '/non-exists-directory"', ], 'with newMigrationNamespace' => [ 'expected' => ['Yiisoft\Db\Migration\Tests\Support\MigrationsExtra\M231108183919Empty'], 'newMigrationNamespace' => 'Yiisoft\Db\Migration\Tests\Support\MigrationsExtra', ], 'with newMigrationPath' => [ - 'expected' => ['M231108183919Empty'], + 'expected' => ['Yiisoft\Db\Migration\Tests\Support\MigrationsExtra\M231108183919Empty'], 'newMigrationNamespace' => '', 'newMigrationPath' => dirname(__DIR__, 2) . '/Support/MigrationsExtra', ], @@ -112,7 +105,6 @@ public static function getNewMigrationsDataProvider(): array ], 'with different sourceNamespaces with the same path' => [ 'expected' => [ - 'Yiisoft\Db\Migration\Tests\ForTest\MigrationsExtra\M231108183919Empty', 'Yiisoft\Db\Migration\Tests\Support\MigrationsExtra\M231108183919Empty', ], 'newMigrationNamespace' => '', @@ -124,8 +116,8 @@ public static function getNewMigrationsDataProvider(): array ], 'with sourcePaths with different paths' => [ 'expected' => [ - 'M231108183919Empty', - 'M231108183919Empty2', + 'Yiisoft\Db\Migration\Tests\Support\MigrationsExtra\M231108183919Empty', + 'Yiisoft\Db\Migration\Tests\Support\MigrationsExtra\M231108183919Empty2', ], 'newMigrationNamespace' => '', 'newMigrationPath' => '', @@ -137,7 +129,6 @@ public static function getNewMigrationsDataProvider(): array ], 'with sourceNamespaces and sourcePaths with the same path' => [ 'expected' => [ - 'M231108183919Empty', 'Yiisoft\Db\Migration\Tests\Support\MigrationsExtra\M231108183919Empty', ], 'newMigrationNamespace' => '', @@ -147,8 +138,8 @@ public static function getNewMigrationsDataProvider(): array ], 'with sourceNamespaces and sourcePaths with different paths' => [ 'expected' => [ - 'M231108183919Empty2', 'Yiisoft\Db\Migration\Tests\Support\MigrationsExtra\M231108183919Empty', + 'Yiisoft\Db\Migration\Tests\Support\MigrationsExtra\M231108183919Empty2', ], 'newMigrationNamespace' => '', 'newMigrationPath' => '', @@ -162,7 +153,7 @@ public static function getNewMigrationsDataProvider(): array 'sourceNamespaces' => ['Yiisoft\Db\Migration\Tests\Support\MigrationsExtra'], ], 'with newMigrationPath and sourcePaths with the same path' => [ - 'expected' => ['M231108183919Empty'], + 'expected' => ['Yiisoft\Db\Migration\Tests\Support\MigrationsExtra\M231108183919Empty'], 'newMigrationNamespace' => '', 'newMigrationPath' => dirname(__DIR__, 2) . '/Support/MigrationsExtra', 'sourceNamespaces' => [], @@ -170,7 +161,6 @@ public static function getNewMigrationsDataProvider(): array ], 'with newMigrationNamespace and sourcePaths with the same path' => [ 'expected' => [ - 'M231108183919Empty', 'Yiisoft\Db\Migration\Tests\Support\MigrationsExtra\M231108183919Empty', ], 'newMigrationNamespace' => 'Yiisoft\Db\Migration\Tests\Support\MigrationsExtra', @@ -192,8 +182,8 @@ public static function getNewMigrationsDataProvider(): array ], 'with newMigrationPath and sourceNamespaces with different paths' => [ 'expected' => [ - 'M231108183919Empty2', 'Yiisoft\Db\Migration\Tests\Support\MigrationsExtra\M231108183919Empty', + 'Yiisoft\Db\Migration\Tests\Support\MigrationsExtra\M231108183919Empty2', ], 'newMigrationNamespace' => '', 'newMigrationPath' => dirname(__DIR__, 2) . '/Support/MigrationsExtra2', @@ -209,6 +199,7 @@ public function testGetNewMigrations( string $newMigrationPath = '', array $sourceNamespaces = [], array $sourcePaths = [], + string $errorMessage = '', ): void { MigrationHelper::useMigrationsNamespace($this->container); @@ -218,9 +209,12 @@ public function testGetNewMigrations( $service->setSourceNamespaces($sourceNamespaces); $service->setSourcePaths($sourcePaths); - $migrations = $service->getNewMigrations(); - - $this->assertSame($expected, $migrations); + try { + $migrations = $service->getNewMigrations(); + $this->assertSame($expected, $migrations); + } catch (LogicException $e) { + $this->assertSame($errorMessage, $e->getMessage()); + } } public function testGetNamespacesFromPathForNoHavingNamespacePath(): void From 6c4bc2e993122096d34fd7b7e548495f22f98ca4 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Tue, 19 May 2026 12:10:26 +0700 Subject: [PATCH 2/5] Fix + add line to CHANGELOG.md --- CHANGELOG.md | 1 + tests/Migration/Service/MigrationServiceTest.php | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4939d98a..356dc34f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ `migrate:create` commands: remove redundant messages, replace `>>>` with cleaner output, and move "Database connection" info to the top (@samdark, @vjik) - Enh #333: Use `newMigrationPath` and `newMigrationNamespace` as source (@Tigrov) +- Bug #341: Fix migration namespaces and paths (@Tigrov) ## 2.0.1 December 20, 2025 diff --git a/tests/Migration/Service/MigrationServiceTest.php b/tests/Migration/Service/MigrationServiceTest.php index 80ecfd0a..2a65bafd 100644 --- a/tests/Migration/Service/MigrationServiceTest.php +++ b/tests/Migration/Service/MigrationServiceTest.php @@ -31,7 +31,8 @@ public function testInvalidNamespace(): void $service->setNewMigrationNamespace('InvalidNamespace\\Hello'); $this->expectException(LogicException::class); - $this->expectExceptionMessage('Invalid namespace: "InvalidNamespace\Hello".'); + $this->expectExceptionMessage('Invalid namespace "InvalidNamespace\Hello"'); + $service->findMigrationPath(); } } From 20f532d00b1724311f43cf4c4bb72414b1c162c2 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Wed, 20 May 2026 12:16:30 +0700 Subject: [PATCH 3/5] Fix --- composer.json | 3 +- src/Service/MigrationService.php | 137 +++++++++++------- .../Service/AbstractMigrationServiceTest.php | 14 +- .../M231108183920DifferentNamespace.php | 16 ++ 4 files changed, 116 insertions(+), 54 deletions(-) create mode 100644 tests/Support/MigrationsExtra/M231108183920DifferentNamespace.php diff --git a/composer.json b/composer.json index 16c29a96..2aa34df0 100644 --- a/composer.json +++ b/composer.json @@ -90,6 +90,7 @@ "check-dependencies": "composer-require-checker", "mutation": "infection", "psalm": "psalm", - "test": "phpunit --testdox --no-interaction" + "test": "phpunit --testdox --no-interaction", + "cs-fix": "php-cs-fixer fix" } } diff --git a/src/Service/MigrationService.php b/src/Service/MigrationService.php index a62e3ef4..ad796481 100644 --- a/src/Service/MigrationService.php +++ b/src/Service/MigrationService.php @@ -16,6 +16,7 @@ use Yiisoft\Db\Migration\Migrator; use Yiisoft\Db\Migration\RevertibleMigrationInterface; +use function array_keys; use function array_map; use function array_unique; use function array_values; @@ -130,41 +131,8 @@ public function getNewMigrations(): array $applied[trim($class, '\\')] = true; } - $migrations = []; - $migrationPaths = $this->findSourcePaths(); - - foreach ($migrationPaths as $sourcePath => $namespace) { - /** @var resource $handle */ - $handle = opendir($sourcePath); - while (($file = readdir($handle)) !== false) { - if ($file === '.' || $file === '..') { - continue; - } - - $path = $sourcePath . DIRECTORY_SEPARATOR . $file; - - if (!is_file($path)) { - continue; - } - - if (preg_match('/^(M(\d{12}).*)\.php$/s', $file, $matches) !== 1) { - continue; - } - - [, $class, $time] = $matches; - - if (class_exists($namespace . '\\' . $class)) { - $class = $namespace . '\\' . $class; - } - - /** @psalm-var class-string $class */ - if (!isset($applied[$class])) { - $migrations[$time . '\\' . $class] = $class; - } - } - closedir($handle); - } - + $migrations = $this->loadMigrationClasses(); + $migrations = array_filter($migrations, static fn(string $class): bool => !isset($applied[$class])); ksort($migrations); return array_values($migrations); } @@ -392,13 +360,11 @@ private function makeMigrationInstance(string $class): object if (!str_contains($class, '\\')) { $isIncluded = false; - $sourcePaths = $this->findSourcePaths(); - - foreach ($sourcePaths as $path => $namespace) { - if (class_exists($namespace . '\\' . $class)) { - continue; - } + $sourcePaths = $this->newMigrationPath !== '' + ? [$this->newMigrationPath, ...$this->sourcePaths] + : $this->sourcePaths; + foreach ($sourcePaths as $path) { $file = $path . DIRECTORY_SEPARATOR . $class . '.php'; if (is_file($file)) { @@ -423,7 +389,8 @@ private function makeMigrationInstance(string $class): object /** * Returns the migration paths with namespaces if they are specified. * - * @return array + * @return true[][] + * @psalm-return array> */ private function findSourcePaths(): array { @@ -434,12 +401,12 @@ private function findSourcePaths(): array throw new LogicException("Invalid path directory \"$this->newMigrationPath\""); } - $newMigrationPath = rtrim(str_replace('\\', '/', $this->newMigrationPath), '/'); - $newMigrationNamespace = $this->getNamespacesFromPath($newMigrationPath)[0] ?? ''; - $paths[$newMigrationPath] = $newMigrationNamespace; + $newMigrationPath = $this->normalizePath($this->newMigrationPath); + $newMigrationNamespaces = $this->getNamespacesFromPath($newMigrationPath); + $paths[$newMigrationPath] = array_fill_keys($newMigrationNamespaces, true); } elseif ($this->newMigrationNamespace !== '') { $newMigrationPath = $this->getNamespacePath($this->newMigrationNamespace); - $paths[$newMigrationPath] = $this->newMigrationNamespace; + $paths[$newMigrationPath][$this->newMigrationNamespace] = true; } foreach ($this->sourcePaths as $sourcePath) { @@ -447,14 +414,14 @@ private function findSourcePaths(): array throw new LogicException("Invalid path directory \"$sourcePath\""); } - $sourcePath = rtrim(str_replace('\\', '/', $sourcePath), '/'); - $sourceNamespace = $this->getNamespacesFromPath($sourcePath)[0] ?? ''; - $paths[$sourcePath] = $sourceNamespace; + $sourcePath = $this->normalizePath($sourcePath); + $sourceNamespaces = $this->getNamespacesFromPath($sourcePath); + $paths[$sourcePath] = ($paths[$sourcePath] ?? []) + array_fill_keys($sourceNamespaces, true); } foreach ($this->sourceNamespaces as $sourceNamespace) { $sourcePath = $this->getNamespacePath($sourceNamespace); - $paths[$sourcePath] = $sourceNamespace; + $paths[$sourcePath][$sourceNamespace] = true; } return $paths; @@ -481,6 +448,7 @@ private function getNamespacePath(string $namespace): string if (str_starts_with($namespace, trim($mapNamespace, '\\'))) { /** @var string $mapDirectory */ $mapDirectory = reset($mapDirectories); + $mapDirectory = $this->normalizePath($mapDirectory); $path = $mapDirectory . '/' . str_replace('\\', '/', substr($namespace, strlen($mapNamespace))); if (is_dir($path)) { @@ -498,6 +466,7 @@ private function getNamespacePath(string $namespace): string * @param string $path File path. * * @return string[] Namespaces. + * @psalm-return list */ private function getNamespacesFromPath(string $path): array { @@ -536,7 +505,8 @@ private function getNamespacesFromPath(string $path): array krsort($namespaces); - return array_values(reset($namespaces)); + /** @psalm-var list */ + return array_values(array_unique(array_merge(...$namespaces))); } private function getVendorDir(): string @@ -544,4 +514,69 @@ private function getVendorDir(): string $class = new ReflectionClass(ClassLoader::class); return dirname($class->getFileName(), 2); } + + /** + * Loads migration classes. + * + * @return string[] List of migration classes indexed by their time stamp and file path. + * @psalm-return class-string[] + */ + private function loadMigrationClasses(): array + { + $migrationPaths = $this->findSourcePaths(); + + $migrations = []; + + foreach ($migrationPaths as $path => $namespaces) { + /** @var resource $handle */ + $handle = opendir($path); + while (($file = readdir($handle)) !== false) { + if ($file === '.' || $file === '..') { + continue; + } + + $filePath = "$path/$file"; + + if (!is_file($filePath)) { + continue; + } + + if (preg_match('/^(M(\d{12}).*)\.php$/s', $file, $matches) !== 1) { + continue; + } + + [, $class, $time] = $matches; + + $sortKey = "$time/$filePath"; + + if (isset($migrations[$sortKey])) { + continue; + } + + require_once $filePath; + + foreach (array_keys($namespaces) as $namespace) { + if (class_exists($namespace . '\\' . $class, false)) { + /** @psalm-var class-string */ + $migrations[$sortKey] = $namespace . '\\' . $class; + break; + } + + if (class_exists($class, false)) { + /** @psalm-var class-string $class */ + $migrations[$sortKey] = $class; + break; + } + } + } + closedir($handle); + } + + return $migrations; + } + + private function normalizePath(string $path): string + { + return rtrim(str_replace('\\', '/', $path), '/'); + } } diff --git a/tests/Common/Service/AbstractMigrationServiceTest.php b/tests/Common/Service/AbstractMigrationServiceTest.php index ce74fac9..30b4d7b7 100644 --- a/tests/Common/Service/AbstractMigrationServiceTest.php +++ b/tests/Common/Service/AbstractMigrationServiceTest.php @@ -84,7 +84,10 @@ public static function getNewMigrationsDataProvider(): array 'newMigrationNamespace' => 'Yiisoft\Db\Migration\Tests\Support\MigrationsExtra', ], 'with newMigrationPath' => [ - 'expected' => ['Yiisoft\Db\Migration\Tests\Support\MigrationsExtra\M231108183919Empty'], + 'expected' => [ + 'Yiisoft\Db\Migration\Tests\Support\MigrationsExtra\M231108183919Empty', + 'Yiisoft\Db\Migration\Tests\ForTest\MigrationsExtra\M231108183920DifferentNamespace', + ], 'newMigrationNamespace' => '', 'newMigrationPath' => dirname(__DIR__, 2) . '/Support/MigrationsExtra', ], @@ -106,6 +109,7 @@ public static function getNewMigrationsDataProvider(): array 'with different sourceNamespaces with the same path' => [ 'expected' => [ 'Yiisoft\Db\Migration\Tests\Support\MigrationsExtra\M231108183919Empty', + 'Yiisoft\Db\Migration\Tests\ForTest\MigrationsExtra\M231108183920DifferentNamespace', ], 'newMigrationNamespace' => '', 'newMigrationPath' => '', @@ -118,6 +122,7 @@ public static function getNewMigrationsDataProvider(): array 'expected' => [ 'Yiisoft\Db\Migration\Tests\Support\MigrationsExtra\M231108183919Empty', 'Yiisoft\Db\Migration\Tests\Support\MigrationsExtra\M231108183919Empty2', + 'Yiisoft\Db\Migration\Tests\ForTest\MigrationsExtra\M231108183920DifferentNamespace', ], 'newMigrationNamespace' => '', 'newMigrationPath' => '', @@ -130,6 +135,7 @@ public static function getNewMigrationsDataProvider(): array 'with sourceNamespaces and sourcePaths with the same path' => [ 'expected' => [ 'Yiisoft\Db\Migration\Tests\Support\MigrationsExtra\M231108183919Empty', + 'Yiisoft\Db\Migration\Tests\ForTest\MigrationsExtra\M231108183920DifferentNamespace', ], 'newMigrationNamespace' => '', 'newMigrationPath' => '', @@ -153,7 +159,10 @@ public static function getNewMigrationsDataProvider(): array 'sourceNamespaces' => ['Yiisoft\Db\Migration\Tests\Support\MigrationsExtra'], ], 'with newMigrationPath and sourcePaths with the same path' => [ - 'expected' => ['Yiisoft\Db\Migration\Tests\Support\MigrationsExtra\M231108183919Empty'], + 'expected' => [ + 'Yiisoft\Db\Migration\Tests\Support\MigrationsExtra\M231108183919Empty', + 'Yiisoft\Db\Migration\Tests\ForTest\MigrationsExtra\M231108183920DifferentNamespace', + ], 'newMigrationNamespace' => '', 'newMigrationPath' => dirname(__DIR__, 2) . '/Support/MigrationsExtra', 'sourceNamespaces' => [], @@ -162,6 +171,7 @@ public static function getNewMigrationsDataProvider(): array 'with newMigrationNamespace and sourcePaths with the same path' => [ 'expected' => [ 'Yiisoft\Db\Migration\Tests\Support\MigrationsExtra\M231108183919Empty', + 'Yiisoft\Db\Migration\Tests\ForTest\MigrationsExtra\M231108183920DifferentNamespace', ], 'newMigrationNamespace' => 'Yiisoft\Db\Migration\Tests\Support\MigrationsExtra', 'newMigrationPath' => '', diff --git a/tests/Support/MigrationsExtra/M231108183920DifferentNamespace.php b/tests/Support/MigrationsExtra/M231108183920DifferentNamespace.php new file mode 100644 index 00000000..64e6f5b3 --- /dev/null +++ b/tests/Support/MigrationsExtra/M231108183920DifferentNamespace.php @@ -0,0 +1,16 @@ + Date: Wed, 20 May 2026 12:19:17 +0700 Subject: [PATCH 4/5] Improve --- src/Service/MigrationService.php | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/Service/MigrationService.php b/src/Service/MigrationService.php index ad796481..d4e67d4c 100644 --- a/src/Service/MigrationService.php +++ b/src/Service/MigrationService.php @@ -397,10 +397,6 @@ private function findSourcePaths(): array $paths = []; if ($this->newMigrationPath !== '') { - if (!is_dir($this->newMigrationPath)) { - throw new LogicException("Invalid path directory \"$this->newMigrationPath\""); - } - $newMigrationPath = $this->normalizePath($this->newMigrationPath); $newMigrationNamespaces = $this->getNamespacesFromPath($newMigrationPath); $paths[$newMigrationPath] = array_fill_keys($newMigrationNamespaces, true); @@ -410,10 +406,6 @@ private function findSourcePaths(): array } foreach ($this->sourcePaths as $sourcePath) { - if (!is_dir($sourcePath)) { - throw new LogicException("Invalid path directory \"$sourcePath\""); - } - $sourcePath = $this->normalizePath($sourcePath); $sourceNamespaces = $this->getNamespacesFromPath($sourcePath); $paths[$sourcePath] = ($paths[$sourcePath] ?? []) + array_fill_keys($sourceNamespaces, true); @@ -577,6 +569,10 @@ private function loadMigrationClasses(): array private function normalizePath(string $path): string { + if (!is_dir($path)) { + throw new LogicException("Invalid path directory \"$path\""); + } + return rtrim(str_replace('\\', '/', $path), '/'); } } From 5c04ec112083a855ae14e3cf7e6b7b0c29cb0382 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Wed, 20 May 2026 17:49:02 +0700 Subject: [PATCH 5/5] Improve --- src/Service/MigrationService.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Service/MigrationService.php b/src/Service/MigrationService.php index d4e67d4c..7be8fbc7 100644 --- a/src/Service/MigrationService.php +++ b/src/Service/MigrationService.php @@ -18,6 +18,7 @@ use function array_keys; use function array_map; +use function array_merge; use function array_unique; use function array_values; use function class_exists; @@ -387,7 +388,7 @@ private function makeMigrationInstance(string $class): object } /** - * Returns the migration paths with namespaces if they are specified. + * Returns the migration paths with namespaces. * * @return true[][] * @psalm-return array> @@ -440,11 +441,10 @@ private function getNamespacePath(string $namespace): string if (str_starts_with($namespace, trim($mapNamespace, '\\'))) { /** @var string $mapDirectory */ $mapDirectory = reset($mapDirectories); - $mapDirectory = $this->normalizePath($mapDirectory); $path = $mapDirectory . '/' . str_replace('\\', '/', substr($namespace, strlen($mapNamespace))); if (is_dir($path)) { - return rtrim($path, '/'); + return $this->normalizePath($path); } } }