Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 38 additions & 1 deletion .github/workflows/mutation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,41 @@ concurrency:

jobs:
tests:
uses: yiisoft/actions/.github/workflows/db-mutation.yml@master
name: PHP 8.5
runs-on: ubuntu-latest

services:
postgres:
image: postgres:18@sha256:5a5a84b19854a9ffaa54082c166ff4ec27473a361e496e5ea167f298f2da9722
env:
POSTGRES_USER: root
POSTGRES_PASSWORD: root
POSTGRES_DB: yiitest
ports:
- 5432:5432
options: --health-cmd="pg_isready" --health-interval=10s --health-timeout=5s --health-retries=3

steps:
- name: Checkout.
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Install PHP with extensions.
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2
with:
php-version: '8.5'
extensions: pdo_pgsql, pdo_sqlite
ini-values: date.timezone='UTC'
tools: infection
coverage: pcov

- name: Install Composer dependencies and database packages.
uses: yiisoft/actions/install-packages@master
with:
packages: >-
['db', 'db-pgsql', 'db-sqlite']

- name: Run infection.
# Command tests share generated migration files and database tables.
run: infection --threads=1 --ignore-msi-with-no-mutations --test-framework-options="--testsuite=Migration,Sqlite,Pgsql"
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## 2.1.2 under development

- New #360: Support named database migration sets with `--db` selection, a single configuration source for `default`, and separate migration history (@samdark)
- Enh #255: Show friendly error on run `./vendor/bin/yii-db-migration` without configuration file (@KalimeroMK)

## 2.1.1 August 10, 2026
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ migrate:up Applies new migrations.

The create command allows defining fields for the table being created.

Use `--db=name` to select a [named database migration set](docs/guide/en/multiple-databases.md).

## Documentation

- Guide: [English](docs/guide/en/README.md), [Português - Brasil](docs/guide/pt-BR/README.md)
Expand Down
97 changes: 48 additions & 49 deletions bin/yii-db-migration
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,10 @@ declare(strict_types=1);
use Psr\Container\ContainerInterface;
use Symfony\Component\Console\Application;
use Yiisoft\Db\Connection\ConnectionInterface;
use Yiisoft\Db\Migration\Command\CreateCommand;
use Yiisoft\Db\Migration\Command\DownCommand;
use Yiisoft\Db\Migration\Command\HistoryCommand;
use Yiisoft\Db\Migration\Command\NewCommand;
use Yiisoft\Db\Migration\Command\RedoCommand;
use Yiisoft\Db\Migration\Command\UpdateCommand;
use Yiisoft\Db\Migration\Command\CommandFactory;
use Yiisoft\Db\Migration\DatabaseSet;
use Yiisoft\Db\Migration\DatabaseSetRegistry;
use Yiisoft\Db\Migration\Informer\ConsoleMigrationInformer;
use Yiisoft\Db\Migration\Migrator;
use Yiisoft\Db\Migration\Runner\DownRunner;
use Yiisoft\Db\Migration\Runner\UpdateRunner;
use Yiisoft\Db\Migration\Service\Generate\CreateService;
use Yiisoft\Db\Migration\Service\MigrationService;
use Yiisoft\Injector\Injector;

$rootPath = dirname(__DIR__, 4);
Expand All @@ -40,49 +32,56 @@ if (!file_exists($configFile)) {
/**
* @psalm-suppress MissingFile
* @psalm-var array{
* container: ContainerInterface|null,
* db: ConnectionInterface|null,
* useTablePrefix: bool,
* historyTable: string,
* migrationNameLimit: int|null,
* maxSqlOutputLength: int|null,
* newMigrationNamespace: string,
* newMigrationPath: string,
* sourceNamespaces: list<string>,
* sourcePaths: list<string>,
* container?: ContainerInterface|null,
* db?: ConnectionInterface|null,
* databases?: array<string, DatabaseSet>,
* useTablePrefix?: bool,
* historyTable?: string,
* migrationNameLimit?: int|null,
* maxSqlOutputLength?: int|null,
* newMigrationNamespace?: string,
* newMigrationPath?: string,
* sourceNamespaces?: list<string>,
* sourcePaths?: list<string>,
* } $params
*/
$params = require $configFile;

$db = $params['db'] ?? throw new LogicException('DB connection is not configured.');
$sets = $params['databases'] ?? [];
$legacyOptions = [
'db', 'newMigrationNamespace', 'newMigrationPath', 'sourceNamespaces', 'sourcePaths',
'historyTable', 'migrationNameLimit', 'useTablePrefix', 'maxSqlOutputLength',
];
if (isset($sets['default'])) {
foreach ($legacyOptions as $option) {
if (array_key_exists($option, $params)) {
throw new LogicException('Configure default migrations either in databases["default"] or with top-level options, not both. Conflicting option: ' . $option . '.');
}
}
} else {
$sets['default'] = new DatabaseSet(
$params['db'] ?? throw new LogicException('DB connection is not configured.'),
newMigrationNamespace: $params['newMigrationNamespace'] ?? '',
newMigrationPath: $params['newMigrationPath'] ?? '',
sourceNamespaces: $params['sourceNamespaces'] ?? [],
sourcePaths: $params['sourcePaths'] ?? [],
historyTable: $params['historyTable'] ?? '{{%migration}}',
migrationNameLimit: array_key_exists('migrationNameLimit', $params) ? $params['migrationNameLimit'] : 180,
useTablePrefix: $params['useTablePrefix'] ?? true,
maxSqlOutputLength: $params['maxSqlOutputLength'] ?? null,
);
}

$injector = new Injector($params['container']);
$migrationInformer = new ConsoleMigrationInformer();
$migrator = new Migrator(
$db,
$migrationInformer,
$params['historyTable'],
$params['migrationNameLimit'],
$params['maxSqlOutputLength']
$databases = new DatabaseSetRegistry(
new Injector($params['container'] ?? null),
new ConsoleMigrationInformer(),
$sets,
);
$createService = new CreateService($db, $params['useTablePrefix']);
$downRunner = new DownRunner($migrator);
$updateRunner = new UpdateRunner($migrator);

$migrationService = new MigrationService($db, $injector, $migrator);
$migrationService->setNewMigrationNamespace($params['newMigrationNamespace']);
$migrationService->setNewMigrationPath($params['newMigrationPath']);
$migrationService->setSourceNamespaces($params['sourceNamespaces']);
$migrationService->setSourcePaths($params['sourcePaths']);

$factory = new CommandFactory($databases);
$application = new Application('Yii Database Migration Tool', '1.0.0');
$application->addCommands([
new CreateCommand($createService, $migrationService, $migrator),
new DownCommand($downRunner, $migrationService, $migrator),
new HistoryCommand($migrationService, $migrator),
new NewCommand($migrationService, $migrator),
new RedoCommand($migrationService, $migrator, $downRunner, $updateRunner),
new UpdateCommand($updateRunner, $migrationService, $migrator),
]);

$commands = [];
foreach (['create', 'down', 'history', 'new', 'redo', 'up'] as $name) {
$commands[] = $factory->create($name);
}
$application->addCommands($commands);
$application->run();
8 changes: 8 additions & 0 deletions bin/yii-db-migration.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@
*/
'db' => null,

/**
* Named database sets (name => Yiisoft\Db\Migration\DatabaseSet instance).
* For a named "default" set, move the legacy options below and "db" into that set, removing their top-level entries.
* Without a "default" entry, the top-level options configure default migrations. Do not supply both forms.
* See docs/guide/en/multiple-databases.md.
*/
'databases' => [],

/**
* Namespace of new migration classes.
*/
Expand Down
35 changes: 30 additions & 5 deletions config/di-console.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,45 @@

declare(strict_types=1);

use Yiisoft\Db\Migration\DatabaseSetRegistry;
use Yiisoft\Db\Migration\Command\CommandFactory;
use Yiisoft\Db\Migration\Command\DatabaseCommand;
use Yiisoft\Db\Migration\Informer\ConsoleMigrationInformer;
use Yiisoft\Db\Migration\Informer\MigrationInformerInterface;
use Yiisoft\Db\Migration\Service\MigrationService;

/** @var array $params */

return [
$definitions = [
DatabaseSetRegistry::class => [
'class' => DatabaseSetRegistry::class,
'__construct()' => [
'databases' => $params['yiisoft/db-migration']['databases'] ?? [],
],
],

MigrationService::class => [
'class' => MigrationService::class,
'setNewMigrationNamespace()' => [$params['yiisoft/db-migration']['newMigrationNamespace']],
'setSourceNamespaces()' => [$params['yiisoft/db-migration']['sourceNamespaces']],
'setNewMigrationPath()' => [$params['yiisoft/db-migration']['newMigrationPath']],
'setSourcePaths()' => [$params['yiisoft/db-migration']['sourcePaths']],
'setNewMigrationNamespace()' => [($params['yiisoft/db-migration']['newMigrationNamespace'] ?? '')],
'setSourceNamespaces()' => [($params['yiisoft/db-migration']['sourceNamespaces'] ?? [])],
'setNewMigrationPath()' => [($params['yiisoft/db-migration']['newMigrationPath'] ?? '')],
'setSourcePaths()' => [($params['yiisoft/db-migration']['sourcePaths'] ?? [])],
],

MigrationInformerInterface::class => ConsoleMigrationInformer::class,
];

if (isset($params['yiisoft/db-migration']['databases']['default'])) {
foreach (['newMigrationNamespace', 'newMigrationPath', 'sourceNamespaces', 'sourcePaths'] as $option) {
if (\array_key_exists($option, $params['yiisoft/db-migration'])) {
throw new LogicException('Configure default migrations either in databases["default"] or with migration parameters, not both. Conflicting option: ' . $option . '.');
}
}
unset($definitions[MigrationService::class]);
foreach (['Create' => 'create', 'Down' => 'down', 'History' => 'history', 'New' => 'new', 'Redo' => 'redo', 'Update' => 'up'] as $class => $name) {
$definitions['Yiisoft\\Db\\Migration\\Command\\' . $class . 'Command']
= static fn(CommandFactory $factory): DatabaseCommand => $factory->create($name);
}
}

return $definitions;
5 changes: 1 addition & 4 deletions config/params.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,6 @@
],

'yiisoft/db-migration' => [
'newMigrationNamespace' => '',
'newMigrationPath' => '',
'sourceNamespaces' => [],
'sourcePaths' => [],
'databases' => [],
],
];
1 change: 1 addition & 0 deletions docs/guide/en/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
- [Yii Console](usage-with-yii-console.md)
- [Symfony application](usage-with-symfony.md)
- [Standalone](usage-standalone.md)
- [Multiple databases](multiple-databases.md)
109 changes: 109 additions & 0 deletions docs/guide/en/multiple-databases.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# Multiple databases

If your application uses separate databases such as `maps` and `analytics`, you can organize their migrations
into isolated sets. Each set has its own migration directory and a separate migration history table in its database,
so migrations applied to one set do not affect the recorded status of migrations in another. Use `--db` to work with
one set, or apply migrations to all sets with a single command.

## Configure your databases

Your existing database is named `default`. Give each additional database a name, such as `maps` or `analytics`, and a
directory for its migrations. Define `default` in the same way when using named sets, with its own connection,
migration directory, and history table. Alternatively, keep your existing configuration for `default` and configure only
the additional sets. Choose one form for `default`; do not configure it in both places. Create the directories before generating migrations:

```text
config/migrations/default/
config/migrations/maps/
config/migrations/analytics/
```

Follow the configuration instructions for your application:

- [Standalone](usage-standalone.md#multiple-databases)
- [Yii Console](usage-with-yii-console.md#multiple-databases)
- [Symfony](usage-with-symfony.md#multiple-databases)

The following examples use the standalone executable.

## Create a migration

To create a migration in the `maps` directory:

```shell
./vendor/bin/yii-db-migration migrate:create create_places --db=maps
```

Edit the generated file with your migration operations as usual. When you apply it with `--db=maps`, those operations
run against the `maps` database.

Without `--db`, `migrate:create` creates the file in the `default` database's migration directory.

## Apply migrations

Apply pending migrations to `maps` only:

```shell
./vendor/bin/yii-db-migration migrate:up --db=maps
```

To apply pending migrations to all configured databases:

```shell
./vendor/bin/yii-db-migration migrate:up
```

The `default` database runs first, followed by the additional databases in configuration order. If `analytics` needs tables
or data created by `maps`, configure `maps` before `analytics`.
You confirm migrations separately for each database. Add `--force-yes` to skip these prompts during deployment.

To apply at most two migrations per database:

```shell
./vendor/bin/yii-db-migration migrate:up --limit=2
```

If a migration fails, execution stops before proceeding to later databases. Changes already applied to earlier databases
remain. Fix the failing migration and run the command again; migrations recorded as applied are skipped.

When selecting migration files with `--path` or `--namespace`, also specify `--db` if you have multiple databases:

```shell
./vendor/bin/yii-db-migration migrate:up --db=analytics --namespace='App\Migrations\Analytics'
```

This requirement also applies to `migrate:new`, so the selected files are checked against one database's history.

## Check pending migrations and history

View pending migrations or applied migrations for `maps`:

```shell
./vendor/bin/yii-db-migration migrate:new --db=maps
./vendor/bin/yii-db-migration migrate:history --db=maps
```

Omit `--db` to view all configured databases. Results are grouped by database. The command succeeds if at least one
database has migrations to show, and returns a failure exit code if all results are empty. An error stops execution even
if an earlier database had results. Use `--all` to show the complete list or `--limit=5` to show up to five migrations per
database.

History is stored in each database's own `migration` table, using its configured table prefix. If you configure multiple
names for the same physical database and want independent histories, give each a different `historyTable` value.

## Revert or redo a migration

Revert the last `maps` migration:

```shell
./vendor/bin/yii-db-migration migrate:down --db=maps
```

Revert and apply the last `maps` migration again:

```shell
./vendor/bin/yii-db-migration migrate:redo --db=maps
```

When multiple databases are configured, both commands require `--db`. Use `--db=default` to select the `default` database.
With only one database configured, you can continue to omit the option.
Loading
Loading