6.x: Capability interfaces for change() vs up()/down() migrations
Nobody has claimed this yet.
- Dominant language
- PHP
- Stars
- 134
- Forks
- 122
- Avg merge
- 2d 2h
- Merged PRs (30d)
- 3
Description
Background
MigrationInterface does not declare up(), down(), change(), or init(). They are runtime hooks dispatched via method_exists():
// src/Migration/Environment.php
if (method_exists($migration, MigrationInterface::CHANGE)) {
$migration->change();
} elseif (method_exists($migration, $direction)) {
$migration->{$direction}();
}
This costs us two permanent PHPStan baseline entries (method.notFound for the up/down dispatch) and prevents IDEs/static analysis from understanding migrations. Adding the methods directly to MigrationInterface is a BC break and is wrong semantically — a migration is either reversible (defines change()) or directional (defines up()/down()), never both.
Proposal
Split the optional hooks into capability interfaces, dispatch via instanceof.
New interfaces
namespace Migrations;
interface ReversibleMigrationInterface extends MigrationInterface
{
public function change(): void;
}
interface DirectionalMigrationInterface extends MigrationInterface
{
public function up(): void;
public function down(): void;
}
(init() could get its own InitializableMigrationInterface if useful, or stay an optional hook — open question.)
Environment dispatch
if ($migration instanceof ReversibleMigrationInterface) {
if ($direction === MigrationInterface::DOWN) {
// ... record-adapter wrapping
$migration->change();
$recordAdapter->executeInvertedCommands();
} else {
$migration->change();
}
} elseif ($migration instanceof DirectionalMigrationInterface) {
$direction === MigrationInterface::UP ? $migration->up() : $migration->down();
}
PHPStan narrows correctly; the two baseline entries are removed.
BaseMigration stance
Proposal: opt-in. BaseMigration does NOT implement either capability interface. Every user migration declares its style explicitly:
class CreateProducts extends BaseMigration implements ReversibleMigrationInterface
{
public function change(): void { /* ... */ }
}
Rationale: cleanest types, the 6.x BC-break window is where this kind of mechanical update is acceptable, and a rector rule can auto-apply the implements clause based on which method the class defines.
Alternative considered: BaseMigration defaults to DirectionalMigrationInterface with empty up()/down(). Zero user friction but PHPStan would treat every migration as having up/down even when only change() is defined — undermines the whole reason for the split.
Migration path for users
- For migrations defining
change(): addimplements ReversibleMigrationInterface. - For migrations defining
up()/down(): addimplements DirectionalMigrationInterface. - Bake templates emit the correct
implementsclause for new migrations. - Provide a rector rule (
AddMigrationCapabilityInterfaceRector) that scans for the method shape and adds the interface — runnable once in the upgrade.
Tasks
- Add
ReversibleMigrationInterface/DirectionalMigrationInterface(and possiblyInitializableMigrationInterface) - Replace
method_existsdispatch inEnvironment::executeMigrationwithinstanceof - Update bake templates to emit
implements ... - Provide rector rule for upgrade
- Drop the two remaining
Environmententries fromphpstan-baseline.neon - Document the new contracts and the upgrade path in the 6.x migration guide
Open questions
- Does
init()warrant its own interface, or is the optional-hook style fine for it? - Should we ship the rector rule in this repo, or as a separate
cakephp-upgradetask?
Context
Came up while reducing the PHPStan baseline in #1083 — the two remaining baseline entries on Environment.php cannot be removed without an interface change.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with src/Migration/Environment.php and inspect executeMigration, then review the existing migration interfaces, bake templates, phpstan-baseline.neon, and migration-guide documentation. The work is complete when capability interfaces and instanceof dispatch are covered, generated migrations and upgrade guidance are updated, the relevant baseline entries are removed, and the rector-rule decision is resolved.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- php
- Domain
- databases
- Issue type
- Refactor
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 38/100