# Routines de mise à jour d’une extension : migrer les données entre versions

> 80 000 fiches produits à faire migrer vers un nouveau format de métadonnées, sans bloquer le site pendant la mise à jour de l'extension : voici l'architecture qui a tenu la charge.

- Auteur : Clément Hadrot
- Publié le : 2021-04-21
- Mis à jour le : 2021-04-21
- Catégorie : Extensions
- URL : https://wpmoderne.dev.wordpress-developpement.fr/extensions/routines-mise-a-jour-migrer-donnees-versions/

## L’essentiel

- Chaque migration doit être idempotente : rejouable sans dégât si elle est interrompue
- Les gros volumes se traitent par lots, jamais en une seule requête bloquante
- La version installée se compare à la version du code à chaque chargement, sans flush inutile

Une chaîne de magasins de bricolage disposait d'un catalogue de 80 000 fiches produits gérées par une extension maison. Le jour où il a fallu changer le format de stockage d'un champ « disponibilité fournisseur », passer d'une simple chaîne de caractères à un tableau structuré, la question s'est posée frontalement : comment migrer un tel volume sans provoquer un timeout ni bloquer le site pendant la mise à jour de l'extension ?

Voici l'architecture de migration retenue, pensée dès le départ pour être rejouable sans risque en cas d'interruption.

## Structurer les migrations en étapes numérotées

Plutôt qu'une unique fonction de migration monolithique, l'extension définit une série de routines numérotées, chacune responsable d'un unique changement de schéma ou de format de données. La version installée avance d'un cran à chaque routine exécutée avec succès.

```
// includes/migrations.php
return array(
    '2.0' => 'kaolin_migration_disponibilite_tableau',
    '2.1' => 'kaolin_migration_ajouter_index_ean',
    '2.2' => 'kaolin_migration_nettoyer_transients_obsoletes',
);
```

Cette structure permet à une installation qui n'a pas été mise à jour depuis plusieurs versions de rejouer automatiquement toutes les migrations manquantes, dans l'ordre, sans intervention manuelle.

## Comparer version installée et version du code

Le déclenchement des migrations se fait sur `plugins_loaded`, en comparant la version stockée en option à la version courante définie dans le fichier principal de l'extension.

```
add_action( 'plugins_loaded', 'kaolin_verifier_migrations' );

function kaolin_verifier_migrations() {
    $version_installee = get_option( 'kaolin_version_donnees', '1.0' );
    $migrations = require plugin_dir_path( __FILE__ ) . 'includes/migrations.php';

    foreach ( $migrations as $version => $callback ) {
        if ( version_compare( $version_installee, $version, '<' ) ) {
            call_user_func( $callback );
            update_option( 'kaolin_version_donnees', $version );
            $version_installee = $version;
        }
    }
}
```

Mettre à jour l'option immédiatement après chaque routine individuelle, plutôt qu'à la toute fin de la boucle, est le détail qui garantit la reprise correcte en cas d'interruption : si le serveur redémarre entre deux migrations, seule celle qui restait à exécuter sera rejouée, pas celles déjà terminées.

## Traiter les gros volumes par lots via WP-Cron

> L'essentiel à retenir : Chaque migration doit être idempotente : rejouable sans dégât si elle est interrompue ; Les gros volumes se traitent par lots, jamais en une seule requête bloquante ; La version installée se compare à la version du code à chaque chargement, sans flush inutile

Exécuter une migration sur 80 000 fiches en une seule requête HTTP synchrone n'était pas envisageable : le risque de timeout serveur était réel, et l'expérience du premier administrateur qui chargerait une page après la mise à jour en aurait fortement pâti. La migration s'est donc appuyée sur une planification par lots via WP-Cron.

```
function kaolin_migration_disponibilite_tableau() {
    update_option( 'kaolin_migration_disponibilite_offset', 0 );
    if ( ! wp_next_scheduled( 'kaolin_traiter_lot_migration' ) ) {
        wp_schedule_single_event( time() + 10, 'kaolin_traiter_lot_migration' );
    }
}

add_action( 'kaolin_traiter_lot_migration', 'kaolin_traiter_lot_migration_callback' );

function kaolin_traiter_lot_migration_callback() {
    $offset = (int) get_option( 'kaolin_migration_disponibilite_offset', 0 );
    $taille_lot = 200;

    $produits = get_posts( array(
        'post_type'      => 'produit',
        'posts_per_page' => $taille_lot,
        'offset'         => $offset,
        'fields'         => 'ids',
    ) );

    foreach ( $produits as $produit_id ) {
        kaolin_convertir_disponibilite( $produit_id );
    }

    if ( count( $produits ) === $taille_lot ) {
        update_option( 'kaolin_migration_disponibilite_offset', $offset + $taille_lot );
        wp_schedule_single_event( time() + 10, 'kaolin_traiter_lot_migration' );
    } else {
        delete_option( 'kaolin_migration_disponibilite_offset' );
    }
}
```

Ce découpage en lots de 200, espacés de dix secondes, a permis à la migration complète de se dérouler en arrière-plan sur environ sept minutes, sans qu'aucun visiteur ni membre de l'équipe ne perçoive de ralentissement du site.

## Rendre chaque migration idempotente

Le point le plus délicat à concevoir : que se passe-t-il si la même routine s'exécute deux fois sur la même fiche, par exemple à cause d'un événement WP-Cron dupliqué ? La fonction de conversion vérifie systématiquement le format déjà présent avant d'agir.

```
function kaolin_convertir_disponibilite( $produit_id ) {
    $valeur = get_post_meta( $produit_id, '_kaolin_disponibilite', true );

    if ( is_array( $valeur ) ) {
        return; // déjà migré, on ne refait rien
    }

    $nouvelle_valeur = array(
        'statut'    => $valeur ?: 'inconnu',
        'migre_le'  => current_time( 'mysql' ),
    );

    update_post_meta( $produit_id, '_kaolin_disponibilite', $nouvelle_valeur );
}
```

Cette vérification en amont, qui coûte une lecture supplémentaire mais élimine tout risque de double conversion, est ce qui distingue une migration robuste d'une migration qui fonctionne uniquement « la première fois, dans de bonnes conditions ».

## Notre verdict

Une architecture de migration versionnée, exécutée par lots via WP-Cron et rendue idempotente à chaque étape, demande davantage de code qu'une fonction unique exécutée au hook d'activation. Sur un volume de données modeste, cette rigueur peut sembler excessive. Mais dès que le catalogue dépasse quelques milliers d'entrées, comme sur ce projet, c'est cette architecture, et elle seule, qui a permis de livrer une mise à jour majeure sans incident visible côté client.
