doctrine/doctrine-migrations-bundle
Integrates Doctrine Migrations into Symfony apps, providing commands and configuration for versioned database schema changes. Generate, run, and rollback migrations across environments with reliable tracking and deployment-friendly workflows.
Installation:
composer require doctrine/doctrine-migrations-bundle
Ensure doctrine/doctrine-bundle is also installed (required dependency).
Configuration:
Add the bundle to config/bundles.php:
return [
// ...
Doctrine\Migrations\Bundle\DoctrineMigrationsBundle::class => ['all' => true],
];
Generate Initial Migration:
php bin/console doctrine:migrations:version --empty
This creates a migrations/Versions directory and a VersionYYYYMMDDHHMMSS.php file.
Create a New Migration:
php bin/console make:migration
This generates a new migration file with a timestamp in migrations/.
Run Migrations:
php bin/console doctrine:migrations:migrate
up() method):
public function up(SchemaManager $sm, Migration $migration): void
{
$this->addSql('ALTER TABLE users ADD COLUMN last_login DATETIME');
}
Local Development:
--dry-run to preview changes:
php bin/console doctrine:migrations:migrate --dry-run
php bin/console doctrine:migrations:migrate PREVIOUS_VERSION
CI/CD Integration:
- name: Run Migrations
run: php bin/console doctrine:migrations:migrate --allow-no-migration
Multi-Environment Handling:
--env=prod to target specific environments:
php bin/console doctrine:migrations:migrate --env=prod
Doctrine Events:
Integrate migrations with Doctrine lifecycle events (e.g., postGenerateSchema):
// config/packages/doctrine.php
doctrine:
orm:
event_subscribers:
- App\EventSubscriber\MigrationSubscriber
Custom Migration Commands: Extend the base migration command for project-specific logic:
namespace App\Command;
use Doctrine\Migrations\Tools\Console\Command\MigrateCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class CustomMigrateCommand extends MigrateCommand
{
protected function execute(InputInterface $input, OutputInterface $output): int
{
// Custom logic before/after migration
return parent::execute($input, $output);
}
}
Service Migrations (v3.7+): Use service-based migrations for complex logic:
namespace App\Migrations;
use Doctrine\Migrations\AbstractMigration;
use Doctrine\DBAL\Schema\Schema;
use Symfony\Component\DependencyInjection\ContainerInterface;
final class Version20230101000000 extends AbstractMigration
{
private ContainerInterface $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function up(Schema $schema): void
{
$this->container->get('some.service')->doSomething();
}
}
Multiple Connections: Configure migrations for multiple Doctrine connections:
# config/packages/doctrine_migrations.yaml
doctrine_migrations:
connections:
default:
enabled: true
secondary:
enabled: true
table_storage:
table_name: 'migration_versions_secondary'
Version Control:
.gitignore:
migrations/Versions/*.php
migrations/Versions directory from deployment.Downgrading Migrations:
down() method) must be idempotent. Test thoroughly:
public function down(SchemaManager $sm, Migration $migration): void
{
$this->addSql('DROP TABLE IF EXISTS temp_table');
}
Schema Filtering:
php bin/console doctrine:migrations:diff --dry-run
Dependency Conflicts:
doctrine/doctrine-bundle (e.g., v3.x requires DoctrineBundle 3.0+).composer.json for version constraints.Profiler Overhead:
# config/packages/dev/doctrine_migrations.yaml
doctrine_migrations:
profiler: false
Verbose Output:
Use --verbose to debug migration execution:
php bin/console doctrine:migrations:migrate --verbose
SQL Logging: Enable Doctrine SQL logging to inspect generated queries:
# config/packages/dev/doctrine.yaml
doctrine:
dbal:
logging: true
logging:
chain:
- doctrine: 'single_table_inheritance'
- stream: '%kernel.logs_dir%/%kernel.environment%.sql.log'
Migration Locking:
migrations.lock) to prevent concurrent runs. Delete this file if stuck:
rm migrations.lock
Naming Conventions:
AddUserProfileTable instead of Version20230101000000).Atomic Migrations:
Testing Migrations:
use Doctrine\Migrations\Tools\Console\Command\MigrateCommand;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
class MigrationTest extends KernelTestCase
{
public function testMigration()
{
$command = $this->getContainer()->get(MigrateCommand::class);
$input = new ArrayInput(['command' => 'doctrine:migrations:migrate']);
$command->run($input, new NullOutput());
}
}
Custom Storage:
# config/packages/doctrine_migrations.yaml
doctrine_migrations:
storage:
table_storage:
table_name: 'migration_versions_custom'
version_column_name: 'version'
executed_at_column_name: 'executed_at'
Post-Migration Hooks:
postMigration events to trigger actions after migration:
// src/EventSubscriber/MigrationSubscriber.php
use Doctrine\Migrations\Event\PostMigrationEventArgs;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class MigrationSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
'migrations.post_migration' => 'onPostMigration',
];
}
public function onPostMigration(PostMigrationEventArgs $args): void
{
// Logic after migration
}
}
Backup Strategy:
Rollback Strategy:
Performance:
public function up(SchemaManager $sm, Migration $migration): void
{
$this->addSql('INSERT INTO users (id, name) VALUES (1, "John"), (2, "Jane")');
}
How can I help you explore Laravel packages today?