doctrine/doctrine-migrations-bundle
The Doctrine Migrations Bundle integrates the Doctrine Migrations library into Symfony, adding console commands and configuration to generate, version, run, and roll back database schema migrations. It supports multiple entity managers and connections for managing migrations across complex apps.
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?