agentleFS
fatonhfatonh/symfony-skills/skills/doctrine-migrations/SKILL.md

doctrine-migrations

Skill35 starsChanged 5 months agoMITView on GitHub

Use when changing the database schema in Symfony — generating Doctrine migrations, writing reversible up()/down(), data migrations, and deploy-safe (zero-downtime) changes. Use when the task mentions migration, schema change, ALTER TABLE, or doctrine:migrations.

1 other project uses a version of this.

Adopted by

Most recently taken up by

The file

We split this skill into the rules it gives an agent

6 rules. About 0 are common, 3 are distinctive, and 3 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈3

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 3

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    After changing entity mapping, generate the migration from the diff, then review it:

    line 14
  2. Very specific:

    Never edit the schema with doctrine:schema:update --force outside local throwaway work — it bypasses migration history.

    line 21
  3. Distinctive:

    Keep migrations immutable once merged/deployed.

    line 49
  4. Distinctive:

    When the app is running during deploy, schema changes must be backward compatible with the currently deployed code.

    line 53
  5. Very specific:

    Don't use App\Entity\Order inside a migration — entities evolve, migrations are frozen in time.

    line 90
  6. Very specific:

    Verify up() then down() then up() runs clean locally before pushing.

    line 95

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Doctrine Migrations (Symfony)

Generate, don't hand-write the schema diff

After changing entity mapping, generate the migration from the diff, then review it:

php bin/console make:migration          # diffs entities vs DB, scaffolds up()/down()
php bin/console doctrine:migrations:migrate

Never edit the schema with doctrine:schema:update --force outside local throwaway work — it bypasses migration history. Always review the generated SQL before committing; the diff tool sometimes proposes destructive or reordering changes you didn't intend.

Anatomy of a migration

final class Version20260620120000 extends AbstractMigration
{
    public function getDescription(): string
    {
        return 'Add status column to orders and index it';
    }

    public function up(Schema $schema): void
    {
        $this->addSql("ALTER TABLE orders ADD status VARCHAR(20) NOT NULL DEFAULT 'pending'");
        $this->addSql('CREATE INDEX idx_orders_status ON orders (status)');
    }

    public function down(Schema $schema): void
    {
        $this->addSql('DROP INDEX idx_orders_status');
        $this->addSql('ALTER TABLE orders DROP status');
    }
}
  • down() must reverse up() exactly — don't leave it empty or throw. A migration you can't roll back is a deploy you can't undo.
  • One logical change per migration — don't bundle unrelated DDL.
  • Keep migrations immutable once merged/deployed. Never edit a migration that has run anywhere; write a new one.

Zero-downtime / deploy-safe changes

When the app is running during deploy, schema changes must be backward compatible with the currently deployed code.

ChangeSafe?Do this
Add nullable column✅Single migration
Add NOT NULL column⚠️Add nullable → backfill → set NOT NULL (3 steps, often 2 deploys)
Rename column❌Add new → copy → deploy code using new → drop old
Drop column⚠️Deploy code that stops using it first, then drop
Add index on large table⚠️CREATE INDEX CONCURRENTLY (Postgres) — outside a transaction
// Postgres concurrent index — must run outside the migration's wrapping transaction
public function up(Schema $schema): void
{
    $this->addSql('CREATE INDEX CONCURRENTLY idx_orders_created_at ON orders (created_at)');
}

public function isTransactional(): bool
{
    return false; // CONCURRENTLY can't run inside a transaction
}

The expand/contract pattern: expand the schema (additive, both old and new code work), deploy code, then contract (remove the old) in a later migration.

Data migrations

Transforming rows belongs in up() too, but keep it set-based SQL, not entity hydration (the entity classes may not match the historical schema):

public function up(Schema $schema): void
{
    $this->addSql("UPDATE orders SET status = 'pending' WHERE status IS NULL");
    $this->addSql('ALTER TABLE orders ALTER COLUMN status SET NOT NULL');
}

Don't use App\Entity\Order inside a migration — entities evolve, migrations are frozen in time. For heavy backfills that can't be one statement, prefer a separate idempotent console command run during deploy.

Conventions

  • One migration file per PR that changes the schema; commit it alongside the entity change.
  • Verify up() then down() then up() runs clean locally before pushing.
  • Name migrations by what they do (via getDescription()), since the class name is just a timestamp.
  • In CI, fail the build if make:migration would produce a non-empty diff (schema and entities are out of sync).
php bin/console doctrine:migrations:migrate --dry-run   # print SQL without executing
php bin/console doctrine:schema:validate                 # entities ⇄ DB in sync?

Gotchas

  • Agent runs doctrine:schema:update --force instead of generating a migration — always use make:migration + migrate.
  • Agent leaves down() empty or throwing — make it reverse up().
  • Agent adds a NOT NULL column with no default to a populated table — add nullable, backfill, then enforce NOT NULL.
  • Agent edits an already-deployed migration — migrations are immutable; write a new one.
  • Agent imports entity classes inside a migration — use raw SQL; entities don't match historical schemas.
  • Agent renames a column in one step during a live deploy — use expand/contract.
  • Agent creates an index on a big Postgres table without CONCURRENTLY (+ isTransactional(): false) — it locks the table.
  • Agent bundles several unrelated schema changes into one migration — keep them separate.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.