laravel-doctrine/migrations
Laravel Doctrine Migrations integrates Doctrine's migration library with Laravel, providing a service provider, publishable configuration, and Artisan commands to run, roll back, and inspect database migrations in Laravel Doctrine applications.
laravel-doctrine/migrations wires the standalone doctrine/migrations library into a Laravel app: it builds a Doctrine DependencyFactory from your Laravel DB config, registers the Doctrine console commands under the doctrine: namespace, and gives you a publishable config file. Release 3.4.4 is a compatibility release (adds Laravel 13 support, CI matrix updated) — no API or config changes, so it is a drop-in bump from 3.4.3. It requires the matching Laravel major, so pin accordingly if you are still on Laravel 10/11/12.
Minimal setup:
composer require laravel-doctrine/migrationsphp artisan vendor:publish --tag=doctrine-migrations (the provider is auto-discovered; check the tag name in the README for your version).config/doctrine-migrations.php — at minimum migrations_paths (namespace → path, e.g. DoctrineMigrations → database_path('migrations')) and the table_storage.table_name (default doctrine_migration_versions).php artisan doctrine:migrations:sync-metadata-storage.Where to look first: the published config file, then php artisan list doctrine — the registered commands mirror Doctrine Migrations' own console names with a doctrine: prefix (doctrine:migrations:migrate, :status, :diff, :generate, :execute, :version, :sync-metadata-storage). Exact names vary by version, so trust artisan list over any blog post.
First real use case: you have an existing schema (or Doctrine entities via laravel-doctrine/orm) and want versioned, reversible, diffable migrations with a schema-aware up(Schema $schema) / down(Schema $schema) API instead of raw SQL strings. Run doctrine:migrations:diff to have Doctrine introspect the database and emit a VersionYYYYMMDDHHMMSS.php class extending Doctrine\Migrations\AbstractMigration, then doctrine:migrations:migrate to apply it.
Migration authoring. Each migration is a class extending Doctrine\Migrations\AbstractMigration with getDescription() plus up(Schema $schema) / down(Schema $schema). Prefer the schema API ($schema->createTable(), $table->addColumn(), $table->addForeignKeyConstraint()) over $this->addSql('...') — it stays platform-portable and is what diff produces, so hand-written and generated migrations look the same to reviewers.
Diff-driven workflow (the one that pays off daily).
doctrine:migrations:diff --allow-empty-diff # review the generated Version*.php
doctrine:migrations:migrate --dry-run # sanity check SQL
doctrine:migrations:migrate
With laravel-doctrine/orm installed, the diff compares your entity mappings against the live DB, which makes diff the primary authoring tool and hand-writing migrations the exception. Without the ORM, it still diffs against the current schema.
Coexistence with Laravel's own migrations. This package does not replace php artisan migrate; it adds a second, independent migration system with its own doctrine_migration_versions table. Two workable patterns:
artisan make:migration, and put doctrine:migrations:migrate in your deploy script. Cleanest, but migrate:fresh, db:wipe, and RefreshDatabase need teaching.Deploy pipeline. Treat migrations as a discrete deploy step, not something a rolling app boot does:
Artisan::call('doctrine:migrations:migrate', ['--no-interaction' => true]);
Better: a shell step before the new release is promoted, so a failing migration aborts the deploy rather than leaving a half-migrated fleet. Add --dry-run/--write-sql output to CI for review, and gate on doctrine:migrations:status if you want an explicit "is the DB current" check.
Test suite integration. RefreshDatabase only knows Laravel's migrator. Either call doctrine:migrations:migrate in setUp/a base TestCase, or (faster) use doctrine:migrations:dump-schema where available and load a SQL fixture. For parallel test runs, each worker needs its own database and its own doctrine_migration_versions table — the version table is per-database, so this resolves itself once each worker has a distinct DB.
Rollback strategy. Doctrine Migrations v3 dropped the old rollback command; you go backwards with doctrine:migrations:migrate --to=<version> (or prev). Always write a real down() — a down() that throws is a legitimate choice for destructive migrations, but make it a deliberate choice, documented in getDescription().
Seeding and data migrations. Doctrine migrations are good at data backfills ($this->addSql("UPDATE ...") plus a batched query if the table is big). Keep them idempotent enough that a re-run after a failed deploy is safe, and never assume the app's Eloquent models are available inside a migration — instantiate query builder / raw SQL instead, so a later model refactor can't break an old migration.
doctrine_migration_versions and migrations are unrelated. php artisan migrate:fresh will happily drop every table while leaving Doctrine's version table populated and lying about what is applied. If you use freshen/wipe in dev, also truncate doctrine_migration_versions (or run doctrine:migrations:version --delete --all --no-interaction).server_version in config/database.php for that connection rather than letting Doctrine probe. This is the single most common source of noise in this stack.all_or_nothing gives you less than it promises; a mid-migration failure leaves partial DDL and a version row that was not written. Keep migrations small and single-purpose so recovery is "fix forward", and be suspicious of long multi-statement migrations on MySQL.diff is a starting point, not gospel. It will happily generate destructive DROP TABLE/DROP COLUMN statements for anything it no longer sees in metadata — the classic cause being "I forgot to hydrate the entity mapping". Read every generated diff. --allow-empty-diff in CI catches the "diff produced nothing but I expected a change" case, which usually means a mapping/config path is wrong.VersionYYYYMMDDHHMMSS.php with the class name matching. Renaming or hand-numbering files breaks ordering and --to targeting. When two branches both generate a diff, the timestamps usually sort fine but the logical order may not — rebase and regenerate rather than trusting the clock.migrations_paths is a namespace→path map, not a list. Registering the same path under two namespaces produces duplicate-class and "migration not found" confusion. One namespace (DoctrineMigrations) for the whole app is fine at almost any scale; add sub-namespaces only if you genuinely need per-module organisation.custom_template in the config lets you shape generated migrations (add headers, enforce a base class, inject a lint-friendly skeleton) — the cheapest way to standardise a team's migrations. For anything deeper, rebind or decorate Doctrine's DependencyFactory; that is the seam every command resolves through, so a container binding there affects all of them.--dry-run, --write-sql, and -vvv in that order. --write-sql dumps the exact SQL Doctrine intends to run without touching the DB, which distinguishes "Doctrine generated the wrong thing" from "the database rejected the right thing" — two very different bugs.How can I help you explore Laravel packages today?