Skip to content

WIP: rewrite migrations to Doctrine Schema tool + PHPStan enforcement#442

Draft
turegjorup wants to merge 4 commits into
feature/consolidate-migrationsfrom
feature/migrations-schema-tool
Draft

WIP: rewrite migrations to Doctrine Schema tool + PHPStan enforcement#442
turegjorup wants to merge 4 commits into
feature/consolidate-migrationsfrom
feature/migrations-schema-tool

Conversation

@turegjorup
Copy link
Copy Markdown
Contributor

Note

Stacked on #441. Targets feature/consolidate-migrations; retarget to release/3.0.0 after #441 merges.

Why now

A major version is the natural cut-point for changing migration conventions, and the consolidated 3.0 migration is the entire current migration population — so it's also a one-time chance to rewrite everything in one place.

Concretely, migrations now go through Doctrine's Schema tool API ($schema->createTable(), $table->addColumn(), $table->addForeignKeyConstraint(), …) instead of raw addSql(...) strings. The emitted DDL is identical for MariaDB but the migration is now generated through the platform abstraction, so the same code produces native DDL on any database Doctrine supports.

This PR lays the groundwork for portability — it does not claim it. Runtime is still MariaDB-only, native SQL in entity listeners is deferred, and CI does not yet exercise other platforms. The conversion is scoped to migrations because we're already touching them in #441 (consolidation + rollup), so the cost of doing it now is low and the convention is set going forward.

Summary

  • Rewrites Version20260506215847.php from 154 raw addSql calls to the equivalent Schema tool builders. A small applyTableOptions() helper at the bottom collapses the engine/charset/collation triplet that would otherwise repeat 29 times.
  • Replaces every type-name string literal ('string', 'datetime_immutable', …) with the corresponding Doctrine\DBAL\Types\Types::* constant — plus UlidType::NAME and RRuleType::RRULE for the two custom types — so renames propagate via the type system rather than across free-form strings.
  • Adds App\PhpStan\NoAddSqlInMigrationRule, a project-local PHPStan rule that flags any $this->addSql(...) call inside migrations/. PHPStan path coverage extended to migrations/.
  • Wires the rule via tools/phpstan/ + composer autoload-dev so it's available at lint time but never loaded into the runtime container.
  • CHANGELOG entry under [Unreleased].

On Rector

No off-the-shelf Rector rule covers either conversion: Rector\Transform\Rector\Attribute\AttributeKeyToClassConstFetchRector handles #[Column(type: 'string')] on entity attributes but not method-call arguments, and there's no rule for addSql → Schema tool. The string-to-constant conversion in this PR was scripted with a small Python regex pass and verified by re-running migrate + schema:validate. A custom Rector rule was considered overkill for a single migration; the PHPStan rule prevents the worse problem (raw addSql) on future migrations.

Test plan

  • Fresh install end-to-end: drop test DB → create → migrations:migrateschema:validate exits 0
  • 2.x → 3.0 upgrade simulation: applied 25 latest-2.7.x migrations to fresh DB → swap files → migrations:rollupschema:validate exits 0
  • mysqldump --no-data diff vs the previous raw-SQL version on MariaDB: only column reordering and index reordering (Schema tool emits columns in addColumn() insertion order). No functional differences. The single FK name divergence on interactive_slide_config (FK_138E544D9033212A vs FK_D30060259033212A) is a pre-existing artifact from the table rename that was already present in WIP: consolidate 25 historical 2.x migrations into one end-of-2.7 schema #441 and didn't break CI there.
  • Full PHPUnit suite: 143 tests / 607 assertions green
  • PHPStan green; rule verified to fire on a smoke-test migration with addSql('SELECT 1') (then deleted)
  • CI green on this PR

Out of scope (deferred)

🤖 Generated with Claude Code

turegjorup and others added 4 commits May 7, 2026 00:48
Replaces every raw `$this->addSql(...)` call in the consolidated
end-of-2.7 migration with the equivalent Schema tool builder API
(`$schema->createTable()`, `$table->addColumn()`,
`$table->addForeignKeyConstraint()`, `$schema->dropTable()`). The
emitted DDL is identical for MariaDB but is now generated through
Doctrine's platform abstraction, so the same migration produces
platform-native DDL on any database Doctrine supports.

Verified end-to-end on MariaDB:
- Fresh install: drop → migrate → schema:validate exits 0
- Upgrade from 2.7: 25 latest-2.x applied → swap files → rollup →
  schema:validate exits 0
- mysqldump diff against the previous raw-SQL version: only column
  ordering and index ordering changes (the Schema tool emits columns
  in addColumn() insertion order). No functional differences.
- Full PHPUnit suite green (143 tests / 607 assertions).

A small `applyTableOptions()` helper at the bottom of the class
collapses the engine/charset/collation triplet that would otherwise
repeat 29 times. Other Doctrine-supported platforms ignore these
MariaDB-specific options.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds `App\PhpStan\NoAddSqlInMigrationRule`, a project-local PHPStan
rule that flags any `$this->addSql(...)` call in files under
`migrations/`. Future migrations are forced through Doctrine's Schema
tool API (`$schema->createTable()`, `$table->addColumn()`, …),
keeping the consolidated 3.0 migration's portability property as the
project evolves.

Wiring:
- Rule lives in `tools/phpstan/`, mapped via composer's autoload-dev
  to `App\PhpStan\` so it's available at lint time but never loaded
  into the runtime container (Symfony's `App\:` resource scan only
  covers `src/`).
- `phpstan.dist.neon` registers the rule and adds `migrations/` to
  the analysed paths.

Native SQL in entity listeners is intentionally out of scope here;
that conversion is deferred. The rule's path-based scoping means it
only enforces the convention for migrations, not the rest of the
codebase.

Verified the rule fires by adding a temporary `_RuleSmokeTest.php`
migration with a single `addSql('SELECT 1')` call — PHPStan reported
the expected `migrations.noAddSql` error and exited non-zero.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Documents the convention shift under [Unreleased]: future migrations
must use Doctrine's Schema tool API; the consolidated 3.0 migration
already does, and the new PHPStan rule enforces it for everything that
follows. Calls out that this lays the groundwork for portability without
claiming it — runtime is still MariaDB-only and CI does not yet cover
other platforms.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the type-name string literals passed to `$table->addColumn()`
with the matching `Doctrine\DBAL\Types\Types::*` constants — and
`UlidType::NAME` / `RRuleType::RRULE` for the two custom types in this
codebase. Refactor-safe (renames at the type-class level propagate via
IDE rename), and signals at a glance that these are Doctrine type
identifiers rather than free-form strings.

No Rector rule covers this conversion for `addColumn()` directly —
`Rector\Transform\Rector\Attribute\AttributeKeyToClassConstFetchRector`
handles `#[Column(type: 'string')]` on entity attributes but not
method-call arguments. A custom Rector rule would be overkill for one
migration; conversion was scripted via a small Python regex pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@turegjorup turegjorup requested a review from tuj May 6, 2026 22:57
@turegjorup turegjorup self-assigned this May 6, 2026
@turegjorup turegjorup changed the title feat: rewrite migrations to Doctrine Schema tool + PHPStan enforcement WIP: rewrite migrations to Doctrine Schema tool + PHPStan enforcement May 6, 2026
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