doctrine/migrations
Doctrine Migrations is a PHP library for managing database schema changes with versioned migrations. Generate, run, and roll back migrations safely across environments, track executed versions, and integrate with Doctrine DBAL/ORM for reliable deployment workflows.
Installation
composer require doctrine/migrations
Configure the Migrations Table
Add this to your config/database.php under a new migrations section:
'migrations' => [
'table' => 'migrations',
'column' => 'version',
'column_hashed' => false,
'order_column' => 'execution_order',
],
Create the Migrations Table Run this Artisan command (adjust for your DB connection):
php artisan doctrine:migrations:create --table=migrations
First Migration Generate a new migration:
php artisan doctrine:migrations:generate --name=CreateUsersTable
Edit the generated file in database/migrations/ (e.g., 20240101000000_CreateUsersTable.php) with your schema changes.
Execute the Migration
php artisan doctrine:migrations:migrate
doctrine:migrations:generate to scaffold a migration for a new table/column.up() to add schema changes and down() to roll them back.php artisan doctrine:migrations:migrate --dry-run.Artisan Command Wrapping Extend Laravel’s Artisan to trigger migrations in deployment workflows:
// app/Console/Commands/Deploy.php
use Doctrine\Migrations\Tools\Console\Command\MigrateCommand;
protected $signature = 'deploy:database';
public function handle() {
$migrate = new MigrateCommand();
$migrate->run(new ArrayInput(['migrate' => null]), new BufferedOutput());
}
Event-Driven Migrations
Hook migrations into Laravel events (e.g., ModelCreating) via service providers:
public function boot() {
Model::creating(function ($model) {
if ($model instanceof User && !Schema::hasTable('users')) {
Artisan::call('doctrine:migrations:migrate');
}
});
}
Environment-Specific Config Use Laravel’s config caching to switch migration paths:
// config/migrations.php
'paths' => [
'local' => database_path('migrations'),
'production' => storage_path('app/migrations'),
],
Override in .env:
MIGRATIONS_PATH=production
Transactional Migrations Wrap schema changes in a transaction for atomicity:
public function up(SchemaManager $sm) {
$sm->beginTransaction();
try {
$sm->createTable('users', [...]);
$sm->commit();
} catch (\Exception $e) {
$sm->rollBack();
throw $e;
}
}
Data Migrations
Use doctrine/dbal for data manipulation:
public function up(Connection $connection) {
$connection->insert('users', ['name' => 'Admin', 'email' => 'admin@example.com']);
}
Dependency Management
Enforce migration order with dependsOn():
public function getDependencies() {
return ['20240101000000_CreateUsersTable'];
}
Migration Table Locking
migrations table, blocking other operations.SET TRANSACTION ISOLATION LEVEL READ COMMITTED in down() or split migrations into smaller batches.Non-Deterministic Migrations
NOW() or random data fail on replay.Doctrine DBAL vs. Laravel Schema
Schema builder and Doctrine\DBAL\Schema can cause conflicts.Dry Runs Test migrations without applying:
php artisan doctrine:migrations:migrate --dry-run
Rollback Debugging
Use --down-to to target specific versions:
php artisan doctrine:migrations:migrate --down-to=20240101000000
Logging Enable verbose output:
php artisan doctrine:migrations:migrate -vvv
Custom Migration Classes
Extend Doctrine\Migrations\AbstractMigration for reusable logic:
class BaseMigration extends AbstractMigration {
protected function addIndex(SchemaManager $sm, string $table, string $column) {
$sm->addIndex($table, [$column]);
}
}
Pre/Post-Migration Hooks
Use Laravel’s registerCommands to intercept migration events:
public function registerCommands() {
$this->commands([
(new MigrateCommand())->setName('migrate:custom'),
]);
}
Migration Testing
Mock the SchemaManager in PHPUnit:
$sm = $this->createMock(SchemaManager::class);
$sm->method('createTable')->willReturn(true);
$migration = new CreateUsersTable();
$migration->up($sm);
Column Hashing
Set column_hashed: true in config to store migration versions as hashes (useful for binary-safe environments).
Execution Order
Use order_column to enforce migration sequence (e.g., for dependent tables).
Database-Specific SQL
Use Doctrine\DBAL\Platforms to abstract platform differences:
$platform = $connection->getDatabasePlatform();
if ($platform->supportsForeignKeyConstraints()) {
$sm->addForeignKeyConstraint(...);
}
How can I help you explore Laravel packages today?