Repository navigation
Conversation
vjik
commented
Oct 15, 2026
| Q | A |
|---|---|
| Is bugfix? | ❌ |
| New feature? | ❌ |
| Breaks BC? | ❌ |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #371 +/- ##
============================================
+ Coverage 97.36% 97.48% +0.11%
- Complexity 373 395 +22
============================================
Files 25 30 +5
Lines 1176 1232 +56
============================================
+ Hits 1145 1201 +56
Misses 31 31 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
🟡 Changes recommended
Registry validation now accepts previously rejected blank database names as migration targets.
1 open finding
What changed in this PR
Refactors multi-database migration support into database configuration, service setup, and command-specific contexts.
Changes:
- Replaces database sets with configuration and registry classes.
- Moves database-selection policies and context creation into commands.
- Updates integration wiring, documentation, and tests.
| File | Description |
|---|---|
| tests/Migration/DatabaseSetRegistryTest.php | Removes old registry tests. |
| tests/Migration/DatabaseRegistryTest.php | Tests replacement registry and setup creation. |
| tests/Migration/ConfigTest.php | Updates registry integration assertions. |
| tests/Migration/BinTest.php | Uses renamed configuration class. |
| tests/Driver/Sqlite/DatabaseCommandsTest.php | Adapts multi-database command tests. |
| src/Service/Generate/CreateService.php | Extracts table-prefix default constant. |
| src/Migrator.php | Extracts migration default constants. |
| src/DatabaseSetup.php | Bundles configuration and migration services. |
| src/DatabaseSetRegistry.php | Removes old registry implementation. |
| src/DatabaseRegistry.php | Adds configuration validation and setup creation. |
| src/DatabaseConfig.php | Renames configuration class and shares defaults. |
| src/Command/UpdateCommand.php | Adds update context and selection policy. |
| src/Command/RedoCommand.php | Adds redo context and selection policy. |
| src/Command/NewCommand.php | Uses migration context and nullable empty results. |
| src/Command/MissingDatabaseOptionPolicy.php | Defines database-selection policies. |
| src/Command/MarkCommand.php | Uses migration context and explicit selection policy. |
| src/Command/HistoryCommand.php | Uses migration context and nullable empty results. |
| src/Command/DownCommand.php | Adds rollback context and selection policy. |
| src/Command/DatabaseCommand.php | Centralizes policy-based dispatch and result handling. |
| src/Command/CreateCommand.php | Creates generation-specific contexts. |
| src/Command/Context/UpdateContext.php | Bundles update services. |
| src/Command/Context/RedoContext.php | Replaces generic context with redo services. |
| src/Command/Context/MigrationContext.php | Bundles shared migration services. |
| src/Command/Context/DownContext.php | Bundles rollback services. |
| src/Command/Context/CreateContext.php | Bundles generation services. |
| docs/guide/en/usage-with-yii-console.md | Updates configuration class reference. |
| docs/guide/en/usage-with-symfony.md | Updates services and discovery exclusions. |
| docs/guide/en/usage-standalone.md | Updates configuration examples. |
| config/di-console.php | Wires replacement registry and configuration. |
| CHANGELOG.md | Links the refactor to multi-database support. |
| bin/yii-db-migration.php | Updates configuration type comment. |
| bin/yii-db-migration | Instantiates replacement registry. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
KalimeroMK
left a comment
There was a problem hiding this comment.
Checked out locally: psalm level 1 clean, 247 tests green, and execute() is behaviourally equivalent to master — listing commands have no FAILURE paths left, so null only replaces the empty-set case (and a real FAILURE from a listing command now propagates instead of being swallowed by continue). Dropping the $context->migrator ?? $this->migrator fallbacks and the $this instanceof DownCommand || ... chain is the win here. Comments below are about duplication and where default now lives — nothing blocking.
| $name = $input->getOption('db'); | ||
| $names = $this->databases?->getNames() ?? [DatabaseSetRegistry::DEFAULT_DATABASE]; | ||
| $multipleDatabases = count($names) > 1; | ||
| $availableNames = [DatabaseRegistry::DEFAULT_DATABASE, ...($this->databases?->getNames() ?? [])]; |
There was a problem hiding this comment.
default is now known in two places: the registry owns DEFAULT_DATABASE but refuses to resolve it, while the command re-adds it here and special-cases it on line 49. Either move the constant here, or let the registry keep owning the concept (getAllNames() default-first + isDefault()).
There was a problem hiding this comment.
The registry only manages additional databases, while default comes from the command's own dependencies. The constant lives in the registry because the registry is what reserves the name and rejects it in the config. Resolving default is the command's job, since only the command has those dependencies. I renamed the command's dependency to $additionalDatabases, so the split is visible at the call site now.
| /** | ||
| * Executes the command for a single database. | ||
| * | ||
| * @psalm-param TContext $context |
There was a problem hiding this comment.
Psalm does substitute TContext here — an undefined property on $context is an error at level 1. But it does not check createContext()/createDefaultContext() against @psalm-return TContext: I made UpdateCommand::createContext() return a MigrationContext and psalm stayed green. A mismatch is a runtime Error, caught only by the tests.
There was a problem hiding this comment.
Good catch. Psalm doesn't substitute class templates when it checks an overriding return type against @psalm-return TContext. I don't see a clean way around that with annotations. Each command has tests that run it against both the default and a named database, so a mismatch fails there right away.
| return new MigrationContext($this->migrator, $this->migrationService); | ||
| } | ||
|
|
||
| protected function createContext(DatabaseSetup $setup): MigrationContext |
There was a problem hiding this comment.
These two are verbatim identical in MarkCommand and NewCommand — six copies. An intermediate abstract class MigrationCommand extends DatabaseCommand<MigrationContext> would hold them once.
There was a problem hiding this comment.
These are one-line constructor calls. A separate abstract level only to hold them makes the hierarchy deeper and hides what each command uses. I'd keep them explicit.
| $io->warning('No migration has been done before.'); | ||
|
|
||
| return Command::FAILURE; | ||
| return null; |
There was a problem hiding this comment.
The convention is only half applied: down/redo still signal the same empty result with Command::FAILURE (DownCommand.php:118,131, RedoCommand.php:125,137) and up with SUCCESS. No difference while those are RejectIfMultiple, but it becomes one if that ever relaxes.
There was a problem hiding this comment.
Fixed: down and redo now return null for an empty result as well. up returns SUCCESS on purpose, because "nothing to apply" is a normal up-to-date state, the same as on master.
| ->addOption('force-yes', 'y', InputOption::VALUE_NONE, 'Force yes to all questions.'); | ||
| } | ||
|
|
||
| protected function getMissingDatabaseOptionPolicy(InputInterface $input): MissingDatabaseOptionPolicy |
There was a problem hiding this comment.
This and getMissingDatabaseOptionError() below are verbatim copies of NewCommand's. A MissingDatabaseOptionPolicy::forSourceOverride($input) helper, or a default in the base class, would cover both.
There was a problem hiding this comment.
It's only two copies, and I'd rather keep the policy explicit in each command, so you see it without jumping to a helper.
|
|
||
| protected function createContext(DatabaseSetup $setup): DownContext | ||
| { | ||
| return new DownContext($setup->migrator, $setup->migrationService, new DownRunner($setup->migrator)); |
There was a problem hiding this comment.
new DownRunner($setup->migrator) also lives in RedoCommand, and new UpdateRunner(...) in two places — construction used to be in one place. Factory methods on DatabaseSetup would keep the laziness and the single site, and let DatabaseSetup::$config go; it exists only so CreateCommand can read db and useTablePrefix.
There was a problem hiding this comment.
new DownRunner($migrator) is a trivial one-liner. Keeping DatabaseSetup a plain data object, and leaving each command to build its own services, keeps DatabaseSetup independent of the command-specific services. I'd keep it as is.
| * | ||
| * @psalm-return list<string> | ||
| */ | ||
| public function getNames(): array |
There was a problem hiding this comment.
Meaning changed from "all, default first" to "additional only". Safe BC-wise since nothing shipped, but the name no longer says what it returns — getAdditionalNames() would.
There was a problem hiding this comment.
The whole registry is about additional databases (see the class docblock), so getNames() there already means "names of the databases in this registry". Adding "additional" to one method would be inconsistent with createSetup(). Instead, I renamed the command's dependency to $additionalDatabases, so the call site now reads $this->additionalDatabases?->getNames().
| public function createSetup(string $name): DatabaseSetup | ||
| { | ||
| $config = $this->configs[$name] | ||
| ?? throw new InvalidArgumentException(sprintf('Unknown additional database: %s.', $name)); |
There was a problem hiding this comment.
Unknown additional database: default. reads wrong for a name that is a valid --db value, just not registry-managed.
There was a problem hiding this comment.
createSetup() is @internal, and the command never calls it for default, so users can't see this message. For a direct call it's accurate: default is not an additional database.
|
|
||
| Each set is an array of `Yiisoft\Db\Migration\DatabaseSet` constructor arguments, where `db` is the ID of a connection | ||
| service. Define these connections in the DI container, for example, in `config/common/db.php`: | ||
| Each set is an array of `Yiisoft\Db\Migration\DatabaseConfig` constructor arguments, where `db` is the ID of |
There was a problem hiding this comment.
The docs still say "set" while the API no longer does — also line 99 here, multiple-databases.md:4-6, README.md:56 and usage-with-symfony.md:99. Worth finishing the rename.
# Conflicts: # tests/Driver/Sqlite/DatabaseCommandsTest.php
KalimeroMK
left a comment
There was a problem hiding this comment.
Looks good — thanks for the fixes and the explanations.
