Un client nous a signalé qu’une bannière « Une mise à jour de la base de données est nécessaire » s’affichait sur chaque écran d’administration de son extension de gestion des devis, plusieurs semaines après l’installation de la dernière version. Le clic sur le bouton de mise à jour semblait fonctionner, la page se rechargeait sans erreur visible, mais la bannière réapparaissait aussitôt, en boucle, sans que la migration n’échoue jamais explicitement.
Ce symptôme précis, une bannière qui persiste malgré une exécution apparemment réussie, pointe presque toujours vers le même endroit du code : la routine de migration ne parvient jamais à écrire, en base, l’indicateur signalant qu’elle est arrivée à son terme.
Comment WordPress décide d’afficher la bannière
Le mécanisme repose sur une simple comparaison de versions, stockée dans une option dédiée à l’extension, généralement mise à jour à la fin de sa routine de migration :
add_action( 'admin_notices', function() {
$version_db_actuelle = get_option( 'devis_version_db', '0' );
if ( version_compare( $version_db_actuelle, DEVIS_VERSION_REQUISE, '<' ) ) {
echo '<div class="notice notice-warning">';
echo '<p>Une mise à jour de la base de données est nécessaire pour cette extension.</p>';
echo '</div>';
}
} );
Tant que devis_version_db reste inférieure à DEVIS_VERSION_REQUISE, la bannière s’affiche, quel que soit le nombre de fois où la migration a été déclenchée. Si la routine de migration ne parvient jamais à mettre à jour cette option, la bannière persistera indéfiniment, même si toutes les modifications de structure ou de données ont, elles, parfaitement réussi.
Le bug retrouvé chez ce client
La routine de migration de l’extension exécutait plusieurs étapes successives, chacune enveloppée dans un bloc try/catch individuel pour ne pas interrompre les suivantes en cas d’échec partiel. Mais la ligne finale, celle qui mettait à jour devis_version_db, se trouvait après une étape précise qui levait une exception non rattrapée dans de rares cas, liée à un enregistrement de devis mal formé datant d’une ancienne version de l’extension.

function devis_executer_migration() {
try {
devis_migrer_etape_1_colonnes();
devis_migrer_etape_2_donnees(); // lève une exception sur certains enregistrements
devis_migrer_etape_3_index();
update_option( 'devis_version_db', DEVIS_VERSION_REQUISE ); // jamais atteinte
} catch ( Exception $e ) {
error_log( 'Erreur migration devis : ' . $e->getMessage() );
}
}
Le bloc catch capturait bien l’exception et l’écrivait dans les journaux, ce qui explique pourquoi aucune erreur n’était visible côté administrateur : la page se rechargeait normalement, sans message d’erreur affiché. Mais l’exécution s’arrêtait avant d’atteindre la ligne update_option(), laissant la bannière condamnée à réapparaître indéfiniment, à chaque nouvel essai infructueux sur le même enregistrement problématique.
Le correctif : isoler l’étape fautive et sécuriser la version
Deux changements ont réglé le problème durablement. D’abord, isoler la migration des données problématiques dans son propre bloc, avec une correction pour ignorer proprement les enregistrements mal formés plutôt que de lever une exception bloquante. Ensuite, structurer la routine pour que la mise à jour de version reflète précisément ce qui a réellement réussi :
function devis_executer_migration() {
devis_migrer_etape_1_colonnes();
try {
devis_migrer_etape_2_donnees();
} catch ( Exception $e ) {
error_log( 'Enregistrement ignoré lors de la migration devis : ' . $e->getMessage() );
// on continue : un enregistrement mal formé ne doit pas bloquer toute la migration
}
devis_migrer_etape_3_index();
update_option( 'devis_version_db', DEVIS_VERSION_REQUISE );
}
Le bloc try/catch a été déplacé pour n’entourer que l’étape réellement susceptible d’échouer sur des données historiques, sans empêcher les étapes suivantes de s’exécuter, ni bloquer la mise à jour finale de version qui, elle, doit toujours pouvoir s’exécuter tant que la structure de la base est cohérente.
Vérifier la version en base directement
Avant de rejouer une migration suspectée bloquée, la commande WP-CLI suivante permet de vérifier immédiatement l’état réel de l’option de version, sans dépendre de l’affichage de la bannière :
wp option get devis_version_db
Prévenir la récidive
- Toujours placer la mise à jour de l’option de version en toute dernière ligne de la routine, jamais avant une étape encore susceptible d’échouer.
- Utiliser
version_compare()plutôt qu’une comparaison de chaînes brute, pour éviter les faux positifs entre des versions comme1.10et1.9. - Journaliser explicitement chaque étape franchie de la migration, pas seulement les erreurs, pour diagnostiquer plus vite une prochaine migration bloquée à mi-parcours.
Une vérification que nous appliquons désormais systématiquement avant de livrer une routine de migration : couper artificiellement une étape intermédiaire en environnement de test, pour s’assurer que l’option de version reste bien à son ancienne valeur, et non figée à moitié chemin vers la nouvelle.
En résumé
Une bannière de mise à jour persistante n’indique presque jamais un problème d’affichage, mais un défaut réel dans la routine de migration elle-même, le plus souvent une exception qui interrompt son exécution avant la ligne finale censée valider la version. Corriger le symptôme visible ne sert à rien tant que le chemin d’exécution qui mène jusqu’à l’écriture de l’option de version n’est pas garanti, même sur des données historiques imparfaites.