Symfony10 min

Doctrine Migrations en Symfony : workflow complet

Par Pierre-Arthur Demengel
SymfonyDoctrineMigrationsDatabase

Les migrations Doctrine sont le mecanisme standard pour faire evoluer le schema de votre base de donnees de maniere versionee et reversible. Chaque modification - ajout de table, de colonne, d'index ou de contrainte - est capturee dans un fichier PHP versionne avec votre code. Ce guide couvre le workflow complet : creation, execution, rollback, data migrations et integration dans un pipeline CI/CD, avec Symfony 7.2 et PHP 8.3.

Installation et configuration

Le bundle de migrations est generalement installe avec le pack ORM, mais vous pouvez l'ajouter separement :

# Si ce n'est pas deja fait
composer require doctrine/doctrine-migrations-bundle

La configuration par defaut est suffisante pour la plupart des projets :

# config/packages/doctrine_migrations.yaml
doctrine_migrations:
    migrations_paths:
        'DoctrineMigrations': '%kernel.project_dir%/migrations'
    enable_profiler: false
    # storage pour tracker les migrations executees
    storage:
        table_storage:
            table_name: 'doctrine_migration_versions'

Le workflow fondamental

Le cycle de vie d'une migration suit quatre etapes :

1. Modifier les entites

<?php
// src/Entity/Product.php
namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
#[ORM\Table(name: 'products')]
class Product
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    #[ORM\Column(length: 255)]
    private string $name;

    #[ORM\Column(type: 'decimal', precision: 10, scale: 2)]
    private string $price;

    // Nouvelle colonne ajoutee
    #[ORM\Column(length: 13, nullable: true)]
    private ?string $ean = null;

    #[ORM\Column(type: 'boolean', options: ['default' => true])]
    private bool $active = true;

    // Getters et setters...
}

2. Generer la migration

# Generer automatiquement a partir du diff entites/schema
php bin/console doctrine:migrations:diff

# Resultat :
# Generated new migration class to "migrations/Version20260930120000.php"

Le fichier genere :

<?php
// migrations/Version20260930120000.php
declare(strict_types=1);

namespace DoctrineMigrations;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

final class Version20260930120000 extends AbstractMigration
{
    public function getDescription(): string
    {
        return 'Add ean and active columns to products table';
    }

    public function up(Schema $schema): void
    {
        $this->addSql('ALTER TABLE products ADD ean VARCHAR(13) DEFAULT NULL');
        $this->addSql('ALTER TABLE products ADD active TINYINT(1) NOT NULL DEFAULT 1');
    }

    public function down(Schema $schema): void
    {
        $this->addSql('ALTER TABLE products DROP COLUMN ean');
        $this->addSql('ALTER TABLE products DROP COLUMN active');
    }
}

3. Executer la migration

# Executer toutes les migrations en attente
php bin/console doctrine:migrations:migrate

# Executer jusqu'a une version specifique
php bin/console doctrine:migrations:migrate 'DoctrineMigrationsVersion20260930120000'

# Mode dry-run (affiche le SQL sans l'executer)
php bin/console doctrine:migrations:migrate --dry-run

4. Verifier le statut

# Voir le statut des migrations
php bin/console doctrine:migrations:status

# Lister toutes les migrations et leur statut
php bin/console doctrine:migrations:list

Rollback : annuler une migration

Chaque migration definit une methode down() qui contient l'operation inverse. Pour revenir en arriere :

# Revenir a la migration precedente
php bin/console doctrine:migrations:migrate prev

# Revenir a une version specifique
php bin/console doctrine:migrations:migrate 'DoctrineMigrationsVersion20260928100000'

# Revenir a zero (annuler TOUTES les migrations)
php bin/console doctrine:migrations:migrate first

Attention : certaines operations ne sont pas reversibles. Si vous ajoutez une colonne puis la supprimez dans le rollback, les donnees sont perdues. Documentez toujours les migrations destructives.

Data migrations : migrer les donnees

Parfois, une migration de schema necessite aussi une transformation de donnees. Utilisez generate pour creer une migration vide dediee :

# Creer une migration vide
php bin/console doctrine:migrations:generate
<?php
// Migration de donnees : normaliser les slugs
final class Version20260930130000 extends AbstractMigration
{
    public function getDescription(): string
    {
        return 'Normalize product slugs to lowercase with hyphens';
    }

    public function up(Schema $schema): void
    {
        // Lire les donnees actuelles
        $products = $this->connection->fetchAllAssociative(
            'SELECT id, name, slug FROM products WHERE slug IS NULL OR slug = \'\''
        );

        foreach ($products as $product) {
            $slug = $this->slugify($product['name']);
            $this->addSql(
                'UPDATE products SET slug = ? WHERE id = ?',
                [$slug, $product['id']]
            );
        }
    }

    public function down(Schema $schema): void
    {
        // Les anciens slugs sont perdus, on met a null
        $this->addSql('UPDATE products SET slug = NULL');
    }

    private function slugify(string $text): string
    {
        $text = transliterator_transliterate('Any-Latin; Latin-ASCII', $text);
        $text = strtolower(trim($text));
        return preg_replace('/[^a-z0-9]+/', '-', $text);
    }
}

Important : n'utilisez jamais les repositories Doctrine dans les migrations. Ils dependent du mapping actuel des entites, qui peut avoir change depuis la creation de la migration. Utilisez directement $this->connection avec du SQL brut.

Migrations safe : bonnes pratiques

Sur des bases de donnees en production avec beaucoup de trafic, certaines operations sont dangereuses :

Operations a eviter en production

  • ALTER TABLE avec LOCK : sur MySQL/MariaDB, certains ALTER verrouillent la table entiere. Utilisez ALGORITHM=INPLACE quand c'est possible
  • DROP COLUMN immediatement : supprimez d'abord les references dans le code, deployez, puis supprimez la colonne dans une migration ulterieure
  • Renommer une colonne : cree un temps d'indisponibilite. Preferez ajouter la nouvelle colonne, copier les donnees, puis supprimer l'ancienne

Pattern safe pour renommer une colonne

// Migration 1 : Ajouter la nouvelle colonne
public function up(Schema $schema): void
{
    $this->addSql('ALTER TABLE users ADD full_name VARCHAR(255) DEFAULT NULL');
    $this->addSql('UPDATE users SET full_name = CONCAT(first_name, \' \', last_name)');
}

// Deployer le code qui lit/ecrit dans full_name ET name

// Migration 2 (apres deploiement) : Supprimer l'ancienne colonne
public function up(Schema $schema): void
{
    $this->addSql('ALTER TABLE users DROP COLUMN first_name');
    $this->addSql('ALTER TABLE users DROP COLUMN last_name');
}

Gestion de version et table de suivi

Doctrine stocke les migrations executees dans une table doctrine_migration_versions. Vous pouvez manipuler cette table pour resoudre des problemes :

# Marquer une migration comme executee sans l'executer
php bin/console doctrine:migrations:version 'DoctrineMigrationsVersion20260930120000' --add

# Marquer comme non executee
php bin/console doctrine:migrations:version 'DoctrineMigrationsVersion20260930120000' --delete

# Utile quand la migration a ete appliquee manuellement en SQL

Integration CI/CD

Dans un pipeline de deploiement continu, les migrations doivent s'executer automatiquement :

# .github/workflows/deploy.yml (extrait)
- name: Run database migrations
  run: |
    php bin/console doctrine:migrations:migrate --no-interaction --allow-no-migration
  env:
    DATABASE_URL: ${{ secrets.DATABASE_URL }}

Ordre recommande pour un deploiement zero-downtime :

  1. Executer les migrations (schema compatible avec l'ancien ET le nouveau code)
  2. Deployer le nouveau code
  3. Executer les migrations de nettoyage (suppression des anciennes colonnes)
# Script de deploiement type
#!/bin/bash
set -e

# 1. Migrations compatibles
php bin/console doctrine:migrations:migrate --no-interaction

# 2. Cache
php bin/console cache:clear --env=prod
php bin/console cache:warmup --env=prod

# 3. Reload PHP-FPM (zero-downtime)
sudo systemctl reload php8.3-fpm

Bases de donnees multiples

Pour les projets avec plusieurs connexions Doctrine :

# config/packages/doctrine.yaml
doctrine:
    dbal:
        default_connection: default
        connections:
            default:
                url: '%env(DATABASE_URL)%'
            analytics:
                url: '%env(ANALYTICS_DATABASE_URL)%'

# config/packages/doctrine_migrations.yaml
doctrine_migrations:
    migrations_paths:
        'DoctrineMigrations': '%kernel.project_dir%/migrations/default'
        'DoctrineMigrations\Analytics': '%kernel.project_dir%/migrations/analytics'
# Generer une migration pour la connexion analytics
php bin/console doctrine:migrations:diff --em=analytics

# Executer les migrations analytics
php bin/console doctrine:migrations:migrate --em=analytics

Template de migration personnalise

Vous pouvez personnaliser le template utilise par generate et diff :

# config/packages/doctrine_migrations.yaml
doctrine_migrations:
    custom_template: '%kernel.project_dir%/config/migration_template.php.tpl'

C'est utile pour ajouter des commentaires standards, des imports specifiques ou des methodes utilitaires a toutes vos migrations.

Conseils issus de la pratique

  • Un commit = une migration : ne regroupez pas des modifications non liees dans une seule migration. Chaque migration doit etre atomique et avoir un objectif clair
  • Testez les rollbacks : executez migrate puis migrate prev en developpement pour chaque nouvelle migration
  • Ne modifiez jamais une migration deja executee en production. Creez une nouvelle migration corrective
  • Revoyez le SQL genere : diff genere parfois du SQL sous-optimal. Verifiez et ajustez si necessaire
  • Versionez les migrations dans Git. Elles font partie integrante de votre codebase

Pour gerer vos entites et requetes Doctrine efficacement, consultez notre reference findBy et findAll. Pour alimenter votre base de test avec des donnees realistes apres les migrations, lisez notre guide sur les fixtures Doctrine. Et pour generer ces donnees automatiquement, decouvrez Faker en Symfony.

Besoin d'accompagnement sur la gestion de votre base de donnees Symfony ? Consultez mes tarifs, explorez mes services, ou contactez-moi pour un audit de votre workflow de migration.

Questions fréquentes

13 projets livrésGrand-Est & BelgiqueLighthouse >90Disponible immédiatement

Un projet en tête ?

Discutons de votre site web. Réponse garantie sous 24h.

Ou appelez directement :06 95 41 30 25

WhatsApp
Appeler