# Tables personnalisées dans une extension : dbDelta et versions de schéma

> Stocker des relevés de compteurs pour des centaines de capteurs dans des post meta aurait tenu, mais mal. Voici quand créer une vraie table, et comment la faire évoluer sans tout casser.

- Auteur : Clément Hadrot
- Publié le : 2020-04-16
- Mis à jour le : 2020-04-16
- Catégorie : Extensions
- URL : https://wpmoderne.dev.wordpress-developpement.fr/extensions/tables-personnalisees-dbdelta-versions-schema/

## L’essentiel

- dbDelta impose un format SQL strict, espaces compris
- Stockez la version du schéma dans une option dédiée
- Chaque montée de version doit être rejouable sans effet de bord

Un client dans la maintenance industrielle m'a confié une extension chargée de collecter des relevés de compteurs, un par capteur et par heure, sur plusieurs centaines de machines. Faire ça avec des `post meta` aurait fonctionné les premières semaines, puis serait devenu ingérable : des millions de lignes dans `wp_postmeta`, une table déjà partagée par tout le reste du site, sans aucun moyen d'indexer efficacement une plage de dates.

C'est exactement le genre de situation où une table personnalisée devient la bonne réponse. Voici comment je l'ai mise en place avec `dbDelta`, et surtout comment j'ai géré les évolutions du schéma sur la durée du projet.

## Décider qu'une table dédiée est justifiée

Créer une table personnalisée est une décision qui engage : elle sort la donnée du système de sauvegarde par défaut des extensions, complique légèrement la portabilité, et demande une vraie discipline de maintenance. Je la réserve à des cas précis.

- Un volume de données qui va croître fortement, sans lien direct avec le cycle de vie d'un article ou d'un utilisateur
- Le besoin de requêtes complexes (jointures, agrégations, index composites) que `WP_Query` ne sait pas exprimer efficacement
- Des données qui n'ont pas vocation à apparaître dans l'éditeur de contenu ni à être des révisions

Pour ce projet, les trois critères étaient réunis : des dizaines de milliers de relevés par jour, des requêtes d'agrégation par capteur et par plage horaire, et aucune pertinence éditoriale pour ces données brutes.

## Écrire un CREATE TABLE compatible dbDelta

`dbDelta()` ne se contente pas d'exécuter du SQL : elle compare la structure demandée à la structure existante et génère les instructions nécessaires pour les faire correspondre. Ce mécanisme impose un format très strict, hérité de contraintes historiques du cœur de WordPress.

```
global $wpdb;

function kaolin_creer_table_releves() {
    global $wpdb;
    $table_name      = $wpdb->prefix . 'kaolin_releves';
    $charset_collate = $wpdb->get_charset_collate();

    $sql = "CREATE TABLE $table_name (
        id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
        capteur_id bigint(20) unsigned NOT NULL,
        valeur decimal(10,2) NOT NULL,
        releve_le datetime NOT NULL,
        PRIMARY KEY  (id),
        KEY capteur_id (capteur_id),
        KEY releve_le (releve_le)
    ) $charset_collate;";

    require_once ABSPATH . 'wp-admin/includes/upgrade.php';
    dbDelta( $sql );
}
```

Quelques règles non négociables, apprises à mes dépens la première fois : deux espaces exactement entre `PRIMARY KEY` et les parenthèses, chaque champ de la définition de clé sur une syntaxe précise, et aucun retour à la ligne inattendu dans les définitions de colonnes. Une seule incohérence d'espacement et `dbDelta` considère que la table diffère à chaque appel, recréant des index en boucle inutilement.

## Stocker et vérifier la version du schéma

> L'essentiel à retenir : dbDelta impose un format SQL strict, espaces compris ; Stockez la version du schéma dans une option dédiée ; Chaque montée de version doit être rejouable sans effet de bord

Appeler `dbDelta()` à chaque chargement de page serait catastrophique en performance. La bonne pratique consiste à stocker un numéro de version du schéma dans une option, et à ne déclencher la fonction que lorsque ce numéro change.

```
define( 'KAOLIN_DB_VERSION', '1.2' );

add_action( 'plugins_loaded', 'kaolin_verifier_version_schema' );

function kaolin_verifier_version_schema() {
    $version_installee = get_option( 'kaolin_db_version', '0' );

    if ( version_compare( $version_installee, KAOLIN_DB_VERSION, '<' ) ) {
        kaolin_creer_table_releves();
        update_option( 'kaolin_db_version', KAOLIN_DB_VERSION );
    }
}
```

Cette vérification tourne à chaque chargement, mais son coût réel est négligeable : une simple lecture d'option, comparée en mémoire. La création ou la modification de table ne s'exécute que le jour d'une mise à jour de l'extension.

## Gérer une montée de schéma en production

Quand un nouveau champ `type_capteur` est devenu nécessaire en cours de projet, deux approches étaient possibles : relancer `dbDelta` avec le SQL complet mis à jour, ou écrire une migration explicite avec `ALTER TABLE`. J'ai choisi la première, car `dbDelta` sait ajouter une colonne manquante sans supprimer les données existantes, à condition que le nom de la table et des colonnes déjà présentes restent identiques.

```
$sql = "CREATE TABLE $table_name (
    id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
    capteur_id bigint(20) unsigned NOT NULL,
    type_capteur varchar(50) NOT NULL DEFAULT '',
    valeur decimal(10,2) NOT NULL,
    releve_le datetime NOT NULL,
    PRIMARY KEY  (id),
    KEY capteur_id (capteur_id),
    KEY releve_le (releve_le)
) $charset_collate;";
```

Attention cependant : `dbDelta` ne supprime jamais une colonne ni un index devenu inutile, même si le SQL fourni ne les mentionne plus. Le nettoyage d'un ancien champ demande un `ALTER TABLE ... DROP COLUMN` manuel, exécuté explicitement dans la routine de migration, jamais automatiquement.

## Ce que j'aurais fait différemment

Avec le recul, j'aurais dès la première version ajouté une colonne `schema_version` au sein même de la table, en complément de l'option globale. Sur un projet où plusieurs tables évoluent indépendamment, une seule option de version pour tout le plugin oblige à relancer toutes les vérifications de schéma à chaque montée de version, même pour des tables qui n'ont pas changé.

## En résumé

`dbDelta` reste l'outil de référence pour créer et faire évoluer une table personnalisée dans WordPress, à condition de respecter à la lettre son format SQL et de piloter son exécution par un numéro de version stocké en option. C'est un mécanisme un peu rigide, mais suffisamment robuste pour avoir accompagné ce projet sur plus de deux ans sans incident de schéma.
