diff --git a/CHANGELOG.md b/CHANGELOG.md index ae6032ea..0959a571 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - Enh #322, #330: Improve output of `migrate:up`, `migrate:down`, `migrate:redo`, `migrate:new`, `migrate:history`, and `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` if `sourceNamespaces` and `sourcePaths` are not specified (@Tigrov) ## 2.0.1 December 20, 2025 diff --git a/composer.json b/composer.json index ad7fda7d..16c29a96 100644 --- a/composer.json +++ b/composer.json @@ -37,16 +37,16 @@ "yiisoft/injector": "^1.2" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.3", - "friendsofphp/php-cs-fixer": "^3.89.2", - "phpunit/phpunit": "^10.5.45", - "rector/rector": "^2.0.10", + "bamarni/composer-bin-plugin": "^1.9.1", + "friendsofphp/php-cs-fixer": "^3.95.1", + "phpunit/phpunit": "^10.5.63", + "rector/rector": "^2.4.2", "yiisoft/db-sqlite": "^2.0", - "yiisoft/di": "^1.3", - "yiisoft/files": "^2.0", - "yiisoft/psr-dummy-provider": "^1.0", - "yiisoft/test-support": "^3.0.2", - "yiisoft/yii-console": "^2.3" + "yiisoft/di": "^1.4.1", + "yiisoft/files": "^2.1", + "yiisoft/psr-dummy-provider": "^1.0.2", + "yiisoft/test-support": "^3.2.0", + "yiisoft/yii-console": "^2.4.2" }, "autoload": { "psr-4": { diff --git a/docs/guide/en/usage-standalone.md b/docs/guide/en/usage-standalone.md index 4db372a0..b4967334 100644 --- a/docs/guide/en/usage-standalone.md +++ b/docs/guide/en/usage-standalone.md @@ -43,9 +43,12 @@ use Yiisoft\Injector\Injector; /** @var ConnectionInterface $database */ $migrator = new Migrator($database, new NullMigrationInformer()); $migrationService = new MigrationService($database, new Injector(), $migrator); -$migrationService->setSourcePaths([dirname(__DIR__, 2), 'migrations']); +$migrationService->setNewMigrationPath(dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'migrations'); ``` +> [!NOTE] +> `sourceNamespaces`, `sourcePaths`, `newMigrationNamespace`, and `newMigrationPath` will be used to find migrations. + Then initialize the command for using without CLI. For example, for applying migrations it will be `UpdateCommand`: ```php @@ -55,7 +58,7 @@ use Yiisoft\Db\Migration\Command\UpdateCommand; use Yiisoft\Db\Migration\Runner\UpdateRunner; $command = new UpdateCommand(new UpdateRunner($migrator), $migrationService, $migrator); -$command->setHelperSet(new HelperSet(['queestion' => new QuestionHelper()])); +$command->setHelperSet(new HelperSet(['question' => new QuestionHelper()])); ``` And, finally, run the command: diff --git a/docs/guide/en/usage-with-yii-console.md b/docs/guide/en/usage-with-yii-console.md index a82e76d8..9394fdd0 100644 --- a/docs/guide/en/usage-with-yii-console.md +++ b/docs/guide/en/usage-with-yii-console.md @@ -33,6 +33,9 @@ Add to `config/console/params.php`: ... ``` +> [!NOTE] +> `sourceNamespaces`, `sourcePaths`, `newMigrationNamespace`, and `newMigrationPath` will be used to find migrations. + Execute `composer du` in console to rebuild the configuration. Now we have the `yiisoft/db-migration` package configured and it can be called in the console. diff --git a/docs/guide/pt-BR/usage-standalone.md b/docs/guide/pt-BR/usage-standalone.md index 78fb7c1b..e97953d1 100644 --- a/docs/guide/pt-BR/usage-standalone.md +++ b/docs/guide/pt-BR/usage-standalone.md @@ -43,9 +43,13 @@ use Yiisoft\Injector\Injector; /** @var ConnectionInterface $database */ $migrator = new Migrator($database, new NullMigrationInformer()); $migrationService = new MigrationService($database, new Injector(), $migrator); -$migrationService->setSourcePaths([dirname(__DIR__, 2), 'migrations']); +$migrationService->setNewMigrationPath(dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'migrations'); ``` +> [!NOTE] +> `sourceNamespaces`, `sourcePaths`, `newMigrationNamespace` e `newMigrationPath` serão usados para encontrar +> as migrações. + Em seguida, inicialize o comando para usar sem CLI. Por exemplo, para aplicar migrações será `UpdateCommand`: ```php @@ -55,7 +59,7 @@ use Yiisoft\Db\Migration\Command\UpdateCommand; use Yiisoft\Db\Migration\Runner\UpdateRunner; $command = new UpdateCommand(new UpdateRunner($migrator), $migrationService, $migrator); -$command->setHelperSet(new HelperSet(['queestion' => new QuestionHelper()])); +$command->setHelperSet(new HelperSet(['question' => new QuestionHelper()])); ``` E, por fim, execute o comando: diff --git a/docs/guide/pt-BR/usage-with-yii-console.md b/docs/guide/pt-BR/usage-with-yii-console.md index 347797c0..1fe3688b 100644 --- a/docs/guide/pt-BR/usage-with-yii-console.md +++ b/docs/guide/pt-BR/usage-with-yii-console.md @@ -33,6 +33,10 @@ Adicione em `config/console/params.php`: ... ``` +> [!NOTE] +> `sourceNamespaces`, `sourcePaths`, `newMigrationNamespace` e `newMigrationPath` serão usados para encontrar +> as migrações. + Execute `composer du` no console para reconstruir a configuração. Agora temos o pacote [`yiisoft/db-migration`](https://github.com/yiisoft/db-migration) configurado e ele pode ser chamado no console. diff --git a/rector.php b/rector.php index b324a6e9..36a1007e 100644 --- a/rector.php +++ b/rector.php @@ -4,6 +4,7 @@ use Rector\CodeQuality\Rector\Class_\InlineConstructorDefaultToPropertyRector; use Rector\Config\RectorConfig; +use Rector\Php55\Rector\String_\StringClassNameToClassConstantRector; use Rector\Php71\Rector\FuncCall\RemoveExtraParametersRector; use Rector\Php81\Rector\FuncCall\NullToStrictStringFuncCallArgRector; use Rector\Php81\Rector\Property\ReadOnlyPropertyRector; @@ -21,5 +22,6 @@ NullToStrictStringFuncCallArgRector::class, ReadOnlyPropertyRector::class, RemoveExtraParametersRector::class => [__DIR__ . '/src/Service/Generate/PhpRenderer.php'], + StringClassNameToClassConstantRector::class => [__DIR__ . '/tests/Common/Service/AbstractMigrationServiceTest.php'], ]) ->withoutParallel(); diff --git a/src/Service/MigrationService.php b/src/Service/MigrationService.php index f6077f47..018e909c 100644 --- a/src/Service/MigrationService.php +++ b/src/Service/MigrationService.php @@ -96,9 +96,13 @@ public function before(string $commandName): int } break; case 'migrate:up': - if (empty($this->sourceNamespaces) && empty($this->sourcePaths)) { + if (empty($this->sourceNamespaces) + && empty($this->sourcePaths) + && empty($this->newMigrationNamespace) + && empty($this->newMigrationPath) + ) { $this->io?->error( - 'At least one of `sourceNamespaces` or `sourcePaths` should be specified.', + 'At least one of `sourceNamespaces`, `sourcePaths`, `newMigrationNamespace` or `newMigrationPath` should be specified.', ); return Command::INVALID; @@ -124,20 +128,10 @@ public function getNewMigrations(): array $applied[trim($class, '\\')] = true; } - $migrationPaths = []; - - foreach ($this->sourcePaths as $path) { - $migrationPaths[] = [$path, '']; - } - - foreach ($this->sourceNamespaces as $namespace) { - $migrationPaths[] = [$this->getNamespacePath($namespace), $namespace]; - } - $migrations = []; - foreach ($migrationPaths as $item) { - [$sourcePath, $namespace] = $item; + $migrationPaths = $this->findSourcePaths(); + foreach ($migrationPaths as [$sourcePath, $namespace]) { if (!is_dir($sourcePath)) { continue; } @@ -167,8 +161,8 @@ public function getNewMigrations(): array } closedir($handle); } - ksort($migrations); + ksort($migrations); return array_values($migrations); } @@ -394,7 +388,7 @@ private function makeMigrationInstance(string $class): object if (!str_contains($class, '\\')) { $isIncluded = false; - foreach ($this->sourcePaths as $path) { + foreach ($this->findSourcePaths() as [$path]) { $file = $path . DIRECTORY_SEPARATOR . $class . '.php'; if (is_file($file)) { @@ -416,6 +410,34 @@ private function makeMigrationInstance(string $class): object return $this->injector->make($class); } + /** + * Returns the migration paths with namespaces if they are specified. + * + * @return array + */ + private function findSourcePaths(): array + { + $paths = []; + + if ($this->newMigrationPath !== '') { + $paths = [[$this->newMigrationPath, '']]; + } elseif ($this->newMigrationNamespace !== '') { + $newMigrationPath = $this->getNamespacePath($this->newMigrationNamespace); + $paths = [[$newMigrationPath, $this->newMigrationNamespace]]; + } + + foreach ($this->sourcePaths as $sourcePaths) { + $paths[] = [$sourcePaths, '']; + } + + foreach ($this->sourceNamespaces as $namespace) { + $sourcePaths = $this->getNamespacePath($namespace); + $paths[] = [$sourcePaths, $namespace]; + } + + return $paths; + } + /** * Returns the file path matching the give namespace. * diff --git a/tests/Common/Command/AbstractUpdateCommandTest.php b/tests/Common/Command/AbstractUpdateCommandTest.php index 46226f72..444ae9c9 100644 --- a/tests/Common/Command/AbstractUpdateCommandTest.php +++ b/tests/Common/Command/AbstractUpdateCommandTest.php @@ -274,18 +274,56 @@ public function testWithoutSourcePath(): void $exitCode = $command->execute([]); $output = preg_replace('/(\R|\s)+/', ' ', $command->getDisplay(true)); + $this->assertSame(Command::SUCCESS, $exitCode); + $this->assertStringContainsString('No new migrations found.', $output); + $this->assertStringContainsString('[OK] Your system is up-to-date.', $output); + } + + public function testWithoutSourceNamespaces(): void + { + MigrationHelper::useMigrationsNamespace($this->container); + + $this->container->get(MigrationService::class)->setSourceNamespaces([]); + + $command = $this->createCommand($this->container); + $command->setInputs(['yes']); + + $exitCode = $command->execute([]); + $output = preg_replace('/(\R|\s)+/', ' ', $command->getDisplay(true)); + + $this->assertSame(Command::SUCCESS, $exitCode); + $this->assertStringContainsString('No new migrations found.', $output); + $this->assertStringContainsString('[OK] Your system is up-to-date.', $output); + } + + public function testWithoutMigrationPaths(): void + { + MigrationHelper::useMigrationsPath($this->container); + + $migration = $this->container->get(MigrationService::class); + $migration->setSourcePaths([]); + $migration->setNewMigrationPath(''); + + $command = $this->createCommand($this->container); + $command->setInputs(['yes']); + + $exitCode = $command->execute([]); + $output = preg_replace('/(\R|\s)+/', ' ', $command->getDisplay(true)); + $this->assertSame(Command::INVALID, $exitCode); - $this->assertStringContainsStringCollapsingSpaces( - 'At least one of `sourceNamespaces` or `sourcePaths` should be specified.', + $this->assertStringContainsString( + 'At least one of `sourceNamespaces`, `sourcePaths`, `newMigrationNamespace` or `newMigrationPath` should be specified.', $output, ); } - public function testWithoutSourceNamespaces(): void + public function testWithoutMigrationNamespaces(): void { MigrationHelper::useMigrationsNamespace($this->container); - $this->container->get(MigrationService::class)->setSourceNamespaces([]); + $migration = $this->container->get(MigrationService::class); + $migration->setSourceNamespaces([]); + $migration->setNewMigrationNamespace(''); $command = $this->createCommand($this->container); $command->setInputs(['yes']); @@ -294,8 +332,8 @@ public function testWithoutSourceNamespaces(): void $output = preg_replace('/(\R|\s)+/', ' ', $command->getDisplay(true)); $this->assertSame(Command::INVALID, $exitCode); - $this->assertStringContainsStringCollapsingSpaces( - 'At least one of `sourceNamespaces` or `sourcePaths` should be specified.', + $this->assertStringContainsString( + 'At least one of `sourceNamespaces`, `sourcePaths`, `newMigrationNamespace` or `newMigrationPath` should be specified.', $output, ); } diff --git a/tests/Common/Service/AbstractMigrationServiceTest.php b/tests/Common/Service/AbstractMigrationServiceTest.php index 80a8074d..e2e53f88 100644 --- a/tests/Common/Service/AbstractMigrationServiceTest.php +++ b/tests/Common/Service/AbstractMigrationServiceTest.php @@ -4,6 +4,7 @@ namespace Yiisoft\Db\Migration\Tests\Common\Service; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Psr\Container\ContainerInterface; use ReflectionMethod; @@ -57,6 +58,171 @@ public function testGetNewMigrationsWithNotExistNamespace(): void $this->assertSame([$className], $migrations); } + public static function getNewMigrationsDataProvider(): array + { + return [ + 'empty' => [ + 'expected' => [], + ], + 'non exists newMigrationNamespace' => [ + 'expected' => [], + 'newMigrationNamespace' => 'Yiisoft\Db\Migration\TestsRuntime\NotExists', + ], + 'non exists newMigrationPath' => [ + 'expected' => [], + 'newMigrationNamespace' => '', + 'newMigrationPath' => dirname(__DIR__, 2) . '/non-exists-directory', + ], + 'non exists sourceNamespaces' => [ + 'expected' => [], + 'newMigrationNamespace' => '', + 'newMigrationPath' => '', + 'sourceNamespaces' => ['Yiisoft\Db\Migration\TestsRuntime\NotExists'], + ], + 'non exists sourcePaths' => [ + 'expected' => [], + 'newMigrationNamespace' => '', + 'newMigrationPath' => '', + 'sourceNamespaces' => [], + 'sourcePaths' => [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'], + 'newMigrationNamespace' => '', + 'newMigrationPath' => dirname(__DIR__, 2) . '/Support/MigrationsExtra', + ], + 'with sourceNamespaces with different paths' => [ + 'expected' => [ + 'Yiisoft\Db\Migration\Tests\Support\Migrations\M231015155500ExecuteSql', + 'Yiisoft\Db\Migration\Tests\Support\Migrations\M231017150317EmptyDown', + 'Yiisoft\Db\Migration\Tests\Support\MigrationsExtra\M231108183919Empty', + 'Yiisoft\Db\Migration\Tests\Support\Migrations\M250312122400ChangeDbPrefixUp', + 'Yiisoft\Db\Migration\Tests\Support\Migrations\M250312122500ChangeDbPrefixDown', + ], + 'newMigrationNamespace' => '', + 'newMigrationPath' => '', + 'sourceNamespaces' => [ + 'Yiisoft\Db\Migration\Tests\Support\Migrations', + 'Yiisoft\Db\Migration\Tests\Support\MigrationsExtra', + ], + ], + 'with different sourceNamespaces with the same path' => [ + 'expected' => [ + 'Yiisoft\Db\Migration\Tests\ForTest\MigrationsExtra\M231108183919Empty', + 'Yiisoft\Db\Migration\Tests\Support\MigrationsExtra\M231108183919Empty', + ], + 'newMigrationNamespace' => '', + 'newMigrationPath' => '', + 'sourceNamespaces' => [ + 'Yiisoft\Db\Migration\Tests\ForTest\MigrationsExtra', + 'Yiisoft\Db\Migration\Tests\Support\MigrationsExtra', + ], + ], + 'with sourcePaths with different paths' => [ + 'expected' => [ + 'M231108183919Empty', + 'M231108183919Empty2', + ], + 'newMigrationNamespace' => '', + 'newMigrationPath' => '', + 'sourceNamespaces' => [], + 'sourcePaths' => [ + dirname(__DIR__, 2) . '/Support/MigrationsExtra', + dirname(__DIR__, 2) . '/Support/MigrationsExtra2', + ], + ], + 'with sourceNamespaces and sourcePaths with the same path' => [ + 'expected' => [ + 'M231108183919Empty', + 'Yiisoft\Db\Migration\Tests\Support\MigrationsExtra\M231108183919Empty', + ], + 'newMigrationNamespace' => '', + 'newMigrationPath' => '', + 'sourceNamespaces' => ['Yiisoft\Db\Migration\Tests\Support\MigrationsExtra'], + 'sourcePaths' => [dirname(__DIR__, 2) . '/Support/MigrationsExtra'], + ], + 'with sourceNamespaces and sourcePaths with different paths' => [ + 'expected' => [ + 'M231108183919Empty2', + 'Yiisoft\Db\Migration\Tests\Support\MigrationsExtra\M231108183919Empty', + ], + 'newMigrationNamespace' => '', + 'newMigrationPath' => '', + 'sourceNamespaces' => ['Yiisoft\Db\Migration\Tests\Support\MigrationsExtra'], + 'sourcePaths' => [dirname(__DIR__, 2) . '/Support/MigrationsExtra2'], + ], + 'with newMigrationNamespace and sourceNamespaces with the same path' => [ + 'expected' => ['Yiisoft\Db\Migration\Tests\Support\MigrationsExtra\M231108183919Empty'], + 'newMigrationNamespace' => 'Yiisoft\Db\Migration\Tests\Support\MigrationsExtra', + 'newMigrationPath' => '', + 'sourceNamespaces' => ['Yiisoft\Db\Migration\Tests\Support\MigrationsExtra'], + ], + 'with newMigrationPath and sourcePaths with the same path' => [ + 'expected' => ['M231108183919Empty'], + 'newMigrationNamespace' => '', + 'newMigrationPath' => dirname(__DIR__, 2) . '/Support/MigrationsExtra', + 'sourceNamespaces' => [], + 'sourcePaths' => [dirname(__DIR__, 2) . '/Support/MigrationsExtra'], + ], + 'with newMigrationNamespace and sourcePaths with the same path' => [ + 'expected' => [ + 'M231108183919Empty', + 'Yiisoft\Db\Migration\Tests\Support\MigrationsExtra\M231108183919Empty', + ], + 'newMigrationNamespace' => 'Yiisoft\Db\Migration\Tests\Support\MigrationsExtra', + 'newMigrationPath' => '', + 'sourceNamespaces' => [], + 'sourcePaths' => [dirname(__DIR__, 2) . '/Support/MigrationsExtra'], + ], + 'with newMigrationNamespace and sourceNamespaces with different paths' => [ + 'expected' => [ + 'Yiisoft\Db\Migration\Tests\Support\Migrations\M231015155500ExecuteSql', + 'Yiisoft\Db\Migration\Tests\Support\Migrations\M231017150317EmptyDown', + 'Yiisoft\Db\Migration\Tests\Support\MigrationsExtra\M231108183919Empty', + 'Yiisoft\Db\Migration\Tests\Support\Migrations\M250312122400ChangeDbPrefixUp', + 'Yiisoft\Db\Migration\Tests\Support\Migrations\M250312122500ChangeDbPrefixDown', + ], + 'newMigrationNamespace' => 'Yiisoft\Db\Migration\Tests\Support\MigrationsExtra', + 'newMigrationPath' => '', + 'sourceNamespaces' => ['Yiisoft\Db\Migration\Tests\Support\Migrations'], + ], + 'with newMigrationPath and sourceNamespaces with different paths' => [ + 'expected' => [ + 'M231108183919Empty2', + 'Yiisoft\Db\Migration\Tests\Support\MigrationsExtra\M231108183919Empty', + ], + 'newMigrationNamespace' => '', + 'newMigrationPath' => dirname(__DIR__, 2) . '/Support/MigrationsExtra2', + 'sourceNamespaces' => ['Yiisoft\Db\Migration\Tests\Support\MigrationsExtra'], + ], + ]; + } + + #[DataProvider('getNewMigrationsDataProvider')] + public function testGetNewMigrations( + array $expected, + string $newMigrationNamespace = '', + string $newMigrationPath = '', + array $sourceNamespaces = [], + array $sourcePaths = [], + ): void { + MigrationHelper::useMigrationsNamespace($this->container); + + $service = $this->container->get(MigrationService::class); + $service->setNewMigrationNamespace($newMigrationNamespace); + $service->setNewMigrationPath($newMigrationPath); + $service->setSourceNamespaces($sourceNamespaces); + $service->setSourcePaths($sourcePaths); + + $migrations = $service->getNewMigrations(); + + $this->assertSame($expected, $migrations); + } + public function testGetNamespacesFromPathForNoHavingNamespacePath(): void { $migrationService = $this->container->get(MigrationService::class);