Skip to content

feat: add ledger-backed database migrations and migrate-* CLI - #286

Open
Ibochkarev wants to merge 4 commits into
modxcms:3.xfrom
Ibochkarev:feat/ledger-backed-migrations
Open

feat: add ledger-backed database migrations and migrate-* CLI#286
Ibochkarev wants to merge 4 commits into
modxcms:3.xfrom
Ibochkarev:feat/ledger-backed-migrations

Conversation

@Ibochkarev

@Ibochkarev Ibochkarev commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Problem

xPDO has schema codegen (parse-schema / write-schema) and Transport packaging, but no ordered, ledger-backed way to apply and reverse database changes across environments. PR #143 (2018–2023) sketched this and closed unmerged: no tests, mysql-centric ledger maps, install-gated commands, branch gone. This PR is a new implementation for current 3.x (PHP ≥8.1; mysql/pgsql/sqlite like existing CI), not a rebase of that diff.

What this PR adds

  • Library: xPDO\Migrations\ with public Migrator, Migration, MigrationContext, MigrationConfig, plus MigratorResult DTOs.
  • Ledger table (default xpdo_migrations): version (UNIQUE), batch, applied_at. Row only after successful up().
  • Console on existing bin/xpdo: migrate-create, migrate-status, migrate, migrate-rollback (always registered; hyphen names like parse-schema).
  • Drivers: portable DDL for mysql, pgsql, sqlite. Adds pgsql to Command::$platforms.
  • Default transaction_policy=non_transactional (MySQL DDL often implicit-commits; stubs and cli.md say so). Policy transactional wraps each migration’s up/down + ledger write.
  • Fail-fast migrate locks; connection pin for the run (restored before ledger write); --step on migrate/rollback.
  • Tests under test/xPDO/Test/Migrations/. CLI docs: docs/migrations/cli.md.

Out of scope (explicit): schema diff, seeds, migrate-fresh, sqlsrv CI gate, checksums.

Compatibility

  • Additive only. Om, Transport, and existing Console commands unchanged aside from registering migrate commands, allowing pgsql on $platforms, and : int on existing ParseSchema / WriteSchema execute() (Symfony Console 6).
  • No MODX packages. Any consumer with xPDO connection config can use it.
  • No new runtime Composer dependencies.

Maintainer value

  • One path for shared DB changes: migration files in git, ledger rows in the DB, same bin/xpdo as schema tools.
  • Failed up is not recorded; later pending stop. You can roll back the last (including partial) batch.
  • Review surface: small public API; discoverer/repository/executor/lock marked @internal.
  • Same problem Migrations for xPDO #143 aimed at, with the tests and driver coverage that PR never landed.

Verified

Gate Result
GitHub Actions (PR branch) PHP 8.1–8.5 × sqlite/mysql/pgsql green
Local sqlite --filter Migration 40 OK

Skips on non-sqlite: sqlite-only flock process test; CLI tests skipped when xpdo_driver is not sqlite (harness uses a sqlite DSN).

Example usage

php bin/xpdo migrate-create CreateWidgetTable \
  -C path/to/properties.inc.php --platform=sqlite \
  --path=./migrations --namespace='App\Migrations'

php bin/xpdo migrate-status -C path/to/properties.inc.php --platform=sqlite \
  --path=./migrations --namespace='App\Migrations'

php bin/xpdo migrate -C path/to/properties.inc.php --platform=sqlite \
  --path=./migrations --namespace='App\Migrations'

php bin/xpdo migrate --step=1 -C path/to/properties.inc.php --platform=sqlite \
  --path=./migrations --namespace='App\Migrations'

php bin/xpdo migrate-rollback -C path/to/properties.inc.php --platform=sqlite \
  --path=./migrations --namespace='App\Migrations'

PHP: new Migrator($xpdo, MigrationConfig::fromArray([...])) then create() / status() / migrate() / rollback().

Reviewer notes

  • Public API: Migrator, Migration, MigrationContext, MigrationConfig, MigratorResult. Rest is @internal.
  • Identity = filename stem YYYYMMDDHHMMSS_Description; ledger version UNIQUE.
  • Default non-transactional is intentional for MySQL DDL; see Transactions in cli.md.
  • Lock fail-fast proven with a second PDO session on mysql/pgsql; sqlite uses flock + second process.

Test plan

  • GitHub Actions CI green (PHP 8.1–8.5 × sqlite/mysql/pgsql)
  • vendor/bin/phpunit -c ./test/sqlite.phpunit.xml --filter Migration
  • vendor/bin/phpunit -c ./test/mysql.phpunit.xml --filter Migration
  • vendor/bin/phpunit -c ./test/pgsql.phpunit.xml --filter Migration
  • php bin/xpdo list shows the four migrate-* commands
  • Smoke: create → status → migrate → rollback on sqlite

MigrationConsoleTest loads Application::loadCommands(), which autoloads
ParseSchema/WriteSchema. Without `: int` PHP fatals under symfony/console ^6|^7.
@Ibochkarev
Ibochkarev marked this pull request as ready for review August 14, 2026 12:58
Tighten consumer docs (identity, transactions, locks) and cover UNIQUE
version inserts plus ledger write after up() clears the connection.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant