Skip to content

Load migration classes only when they are executed - #1114

Open
nicosp wants to merge 1 commit into
cakephp:5.xfrom
QoboLtd:lazy-load-migrations
Open

Load migration classes only when they are executed#1114
nicosp wants to merge 1 commit into
cakephp:5.xfrom
QoboLtd:lazy-load-migrations

Conversation

@nicosp

@nicosp nicosp commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Checking whether migrations are up to date currently requires loading and instantiating every migration class. This happens in migrations status, PendingMigrationsMiddleware and the test suite Migrator, and it gets slower as projects accumulate migrations.

This change reads migration versions and names from the file names and loads a migration class only when it is about to run, be rolled back or have its breakpoint changed.

Changes

  • New Manager::getMigrationVersions() returns the sorted versions without loading any classes. It still checks for duplicate versions and names, since that only needs the file names.
  • New Manager::getMigration(int $version) loads a single migration and caches it.
  • printStatus(), migrateToDateTime(), getVersionsToMark() and cleanupMissingMigrations() work from versions only.
  • migrate() and rollback() load only the migrations they execute.
  • markBreakpoint() loads only the migration being marked.
  • PendingMigrationsMiddleware uses getMigrationVersions().
  • getMigrations() is kept for backwards compatibility and still loads all migrations. Migrations passed to setMigrations() still take priority.

Behaviour notes

  • printStatus() takes the migration name from the file name. For class-based migrations this is the same as getName(). For anonymous-class migrations it shows the name from the file name instead of PHP's generated class@anonymous… name.
  • A broken migration file, such as one using the legacy AbstractMigration base class or missing its class, no longer breaks status checks or the middleware. The error is raised when that migration is executed.

Tests

Added tests using a fixture that throws when loaded. They check that getMigrationVersions(), printStatus() and an up-to-date migrate() do not load migration classes. The full suite passes locally on SQLite, MySQL and Postgres, and phpstan and phpcs are clean.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EeBHdY3PAMKrNXQnogCUsQ

Checking migration status (status command, PendingMigrationsMiddleware,
the test suite Migrator) previously required every migration class to be
loaded and instantiated. Migration versions and names are now derived from
the file names, and a migration class is only loaded when the migration is
about to be executed, rolled back or have its breakpoint changed.

Adds Manager::getMigrationVersions() and Manager::getMigration().
getMigrations() still loads all migrations for backwards compatibility.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EeBHdY3PAMKrNXQnogCUsQ
@dereuromark

Copy link
Copy Markdown
Member

Did you measure the diff?
And how many migrations are we talking about?

The downside of this is getting no real feedback on invalid/broken ones until you actually execute I assume.

@nicosp

nicosp commented Sep 14, 2026

Copy link
Copy Markdown
Contributor Author

Did you measure the diff? And how many migrations are we talking about?

The downside of this is getting no real feedback on invalid/broken ones until you actually execute I assume.

The motivation was to speed up our test bootstrap. Running a single test spent around 45s in the migrations. With this it dropped to around 4s. We have around 900 migrations :) Our application is not the common case for sure.

@nicosp
nicosp marked this pull request as ready for review September 14, 2026 11:04
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.

2 participants