Aller au contenu
Configuration

Search and replace de l'URL dans la base WordPress sans casser les données sérialisées

Changer l'URL d'un site WordPress dans la base sans vider les widgets, les menus ni les réglages du thème. La commande WP-CLI qui préserve les données sérialisées.

Publié

Ne lancez jamais un simple UPDATE ... SET column = REPLACE(column, 'oldurl', 'newurl') sur une base WordPress. Il modifiera correctement le texte et détruira vos réglages en même temps, parce que WordPress stocke les tableaux et les objets sous forme de chaînes sérialisées PHP, et que ce format enregistre la longueur en octets de chaque chaîne qu’il contient. Un remplacement SQL brut réécrit le texte mais ne touche jamais à la longueur enregistrée, et la valeur devient illisible.

Utilisez plutôt un outil qui comprend la sérialisation. En ligne de commande, c’est wp search-replace. Sauvegardez d’abord la base, lancez une simulation, puis exécutez la commande pour de vrai.

Ce qui casse exactement

Une chaîne sérialisée PHP ressemble à ceci :

s:18:"http://example.com"

Le 18, c’est la longueur en octets du texte qui suit. Comptez : http://example.com fait exactement 18 caractères.

Imaginons maintenant un réglage de thème enregistré :

a:1:{s:4:"logo";s:18:"http://example.com";}

Lancez un remplacement SQL brut de http:// vers https:// et vous obtenez :

a:1:{s:4:"logo";s:19:"https://example.com";}

Sauf que non. Vous obtenez ceci :

a:1:{s:4:"logo";s:18:"https://example.com";}

La chaîne fait désormais 19 octets mais prétend toujours en faire 18. PHP lit la longueur déclarée, avance de 18 octets, ne trouve pas le guillemet fermant et le point-virgule là où il les attend, et abandonne. Le tableau entier échoue à se désérialiser.

Pourquoi l’échec est silencieux

C’est ce qui rend ce bug si coûteux. WordPress ne déclenche pas d’erreur et ne s’arrête pas. Quand il lit une option, il passe la valeur dans sa fonction de désérialisation, PHP renvoie false pour des données malformées, et WordPress transmet à votre code une valeur qui se comporte comme si l’option n’avait jamais été enregistrée. L’extension ou le thème retombe alors sur sa valeur par défaut.

Le symptôme n’est donc pas une page d’erreur. Le symptôme, c’est :

  • Des widgets qui disparaissent des colonnes latérales
  • Des réglages de personnalisation qui reviennent aux valeurs par défaut du thème
  • Les données de mise en page d’un constructeur de pages qui reviennent vides et une page qui s’affiche blanche
  • Des pages de réglages d’extension qui ont l’air fraîchement installées
  • Des menus qui perdent leur emplacement assigné

Rien dans l’administration ne signale quoi que ce soit. La ligne est toujours dans la base, toujours remplie de ce qui ressemble à du texte correct. Seul le préfixe de longueur est faux d’un ou deux octets. PHP émet bien une notice quand la désérialisation échoue, mais sur un site en production avec l’affichage des erreurs désactivé, personne ne la voit. Activer WP_DEBUG avec WP_DEBUG_LOG actif et WP_DEBUG_DISPLAY à false écrira ces notices dans un fichier de log où vous pourrez les lire.

Les dégâts ne sont pas non plus répartis uniformément. Les valeurs scalaires simples — siteurl, home, le contenu d’un article, une URL seule dans sa propre colonne — survivent parfaitement à un remplacement naïf. Seules les valeurs sérialisées cassent. C’est pour ça qu’un mauvais remplacement donne souvent l’impression d’avoir fonctionné au premier coup d’œil.

Vérifiez wp-config.php avant de toucher à quoi que ce soit

Si wp-config.php définit WP_HOME ou WP_SITEURL, ces constantes l’emportent sur ce que contient la base. Modifier wp_options semblera n’avoir aucun effet. Cherchez des lignes comme celles-ci avant de conclure que le problème vient de la base :

define( 'WP_HOME', 'http://example.com' );
define( 'WP_SITEURL', 'http://example.com' );

Mettez-les à jour avec la nouvelle URL, ou supprimez-les et laissez la base décider.

Sauvegardez d’abord, sérieusement

Une sérialisation cassée n’est pas toujours facile à réparer. Une fois que le préfixe de longueur et le contenu ne concordent plus, la valeur d’origine n’est pas récupérable à partir de la valeur cassée — impossible de savoir quelle longueur était correcte sans connaître la chaîne d’origine. Une sauvegarde est le seul vrai bouton « annuler ».

wp db export backup-before-replace.sql

Ou avec mysqldump si WP-CLI n’est pas disponible :

mysqldump -u USER -p DBNAME > backup-before-replace.sql

Gardez-la hors du serveur si possible. Une sauvegarde posée à la racine web est à la fois un point de restauration et un problème de sécurité.

La commande sûre

Le search-replace de WP-CLI désérialise chaque valeur, parcourt la structure décodée, remplace à l’intérieur, puis re-sérialise avec les bonnes longueurs. Simulation d’abord :

wp search-replace 'http://example.com' 'https://example.com' --dry-run --all-tables --skip-columns=guid

Lisez le rapport. Il indique combien de remplacements auront lieu par table et par colonne. Si la liste des tables semble incorrecte, ou si une table que vous ne reconnaissez pas affiche des milliers d’occurrences, arrêtez-vous et regardez avant de lancer la vraie commande.

Puis exécutez-la :

wp search-replace 'http://example.com' 'https://example.com' --all-tables --skip-columns=guid --report-changed-only

Quelques remarques honnêtes sur les options :

  • --all-tables couvre les tables que WordPress n’enregistre pas lui-même, là où vivent souvent les données d’extensions. Sans cette option, certaines tables d’extensions sont totalement ignorées.
  • --skip-columns=guid a son importance. Le guid est un identifiant permanent pour les lecteurs de flux, pas une URL active. Le réécrire peut faire voir toute votre archive comme de nouveaux articles aux abonnés du flux.
  • Cherchez sans le slash final et sans les variantes de protocole que vous ne voulez pas toucher. Remplacer example.com tout court touchera aussi les adresses e-mail et toute mention dans le texte des articles.

Si vous ne pouvez pas utiliser la ligne de commande, les extensions de migration compatibles avec la sérialisation font le même travail de décodage depuis l’administration. Le comportement à rechercher est explicitement décrit comme la prise en charge des données sérialisées ; si un outil ne le dit pas, partez du principe qu’il ne le fait pas.

Les tables et colonnes concernées

TableColonneRisque lié à la sérialisation
wp_optionsoption_valueÉlevé — widgets, theme mods, transients, la plupart des réglages d’extensions
wp_postmetameta_valueÉlevé — mises en page de constructeurs, tableaux de champs personnalisés
wp_usermetameta_valueÉlevé — capacités, préférences d’écran et de tableau de bord
wp_termmetameta_valueMoyen — dépend des extensions
wp_postspost_content, guidFaible pour le contenu, mais les attributs de shortcodes et le balisage des blocs contiennent des URL
wp_commentscomment_content, comment_author_urlFaible
wp_linkslink_url, link_imageFaible, et souvent inutilisée

En multisite, ajoutez wp_blogs, wp_site, wp_sitemeta, ainsi que les wp_2_options, wp_3_options et suivantes propres à chaque site. Un changement d’URL en multisite obéit à des règles supplémentaires qui dépassent le simple search and replace : traitez ça comme un chantier à part.

Les cas que search-replace ne règle pas complètement

Soyons honnêtes sur les limites :

Du JSON stocké dans la base. Certains constructeurs de pages enregistrent les données de mise en page en JSON avec des barres obliques échappées, si bien que votre URL apparaît sous la forme https:\/\/example.com et non https://example.com. Une recherche sur la forme normale passera à côté. Il vous faudra peut-être un second passage sur la chaîne échappée. Testez sur une copie.

Des valeurs encodées en base64. Si une extension encode ses réglages avant de les stocker, aucun remplacement textuel ne peut voir l’URL. Ces réglages doivent en général être ressaisis depuis l’écran de l’extension.

Des URL en dur dans les fichiers. Tout ce qui est écrit dans functions.php, dans un template de thème enfant ou dans un chemin d’asset codé en dur n’est pas dans la base et ne sera pas touché. Passez le dossier du thème au grep séparément.

Le cache et les sorties générées. Le cache objet, le cache de pages et les copies du CDN continuent de servir les anciennes URL après un remplacement pourtant correct. Videz-les, puis vérifiez de nouveau avant de conclure que le remplacement a échoué.

Si vous avez déjà lancé la mauvaise requête

Restaurez la sauvegarde. C’est la réponse, et il vaut mieux le dire franchement que de proposer une procédure de réparation qui ne fonctionne presque jamais.

S’il n’y a pas de sauvegarde, la marge de récupération est étroite : les valeurs qui n’ont jamais été sérialisées vont bien, et celles qui sont cassées doivent être reconstruites à la main ou réinitialisées en ré-enregistrant l’écran de réglages concerné. Ré-enregistrer une zone de widgets, re-sélectionner un emplacement de menu ou ressaisir les réglages d’une extension écrit une valeur neuve, correctement sérialisée, par-dessus la valeur cassée. Fastidieux, mais efficace, et c’est mieux que de laisser des lignes corrompues en place où elles embrouilleront la prochaine personne qui regardera le site.

Pour générer les bonnes requêtes avant de lancer quoi que ce soit, construisez-les avec l’outil SQL de search and replace WordPress — il montre quelles colonnes peuvent être traitées en SQL brut et lesquelles exigent une prise en charge de la sérialisation, ce qui vous laisse voir la répartition avant de vous engager sur une commande.

FAQ

Questions

Pourquoi un simple REPLACE en SQL casse-t-il un site WordPress ?

Parce que WordPress stocke les tableaux et les objets sous forme de texte sérialisé PHP, et que ce format enregistre la longueur en octets de chaque chaîne qu'il contient. Un REPLACE brut modifie le texte mais laisse l'ancienne longueur en place. PHP refuse alors de désérialiser la valeur, WordPress retombe sur une valeur par défaut, et les réglages apparaissent vides plutôt que faux.

Quelle est la façon la plus sûre de changer l'URL d'un site WordPress dans la base ?

Faites une sauvegarde complète de la base, puis lancez la commande WP-CLI wp search-replace, d'abord en simulation. WP-CLI désérialise chaque valeur, remplace à l'intérieur de la structure décodée, puis re-sérialise avec les bonnes longueurs. Les extensions de migration compatibles avec la sérialisation font le même travail depuis l'administration si vous n'avez pas accès à la ligne de commande.

Quelles tables contiennent l'ancienne URL ?

Principalement wp_options, wp_posts, wp_postmeta, wp_usermeta, wp_termmeta et wp_comments. Le danger de la sérialisation se concentre dans wp_options et dans toutes les colonnes meta_value, puisque les widgets, les theme mods, les transients et les données de constructeur de pages sont stockés sous forme de tableaux. En multisite, wp_blogs, wp_site et wp_sitemeta portent aussi le domaine.

Modifier la base suffit-il si wp-config.php définit l'URL ?

Non. Si wp-config.php définit WP_HOME ou WP_SITEURL, ces constantes l'emportent sur les valeurs siteurl et home stockées dans wp_options. Modifier la base ne change rien de visible tant que les constantes ne sont pas mises à jour ou supprimées. Vérifiez wp-config.php en premier : cette seule ligne explique beaucoup de migrations ratées.

Faut-il aussi remplacer la colonne guid ?

Non. Le guid est un identifiant permanent destiné aux lecteurs de flux, pas une URL fonctionnelle, et le réécrire peut faire réapparaître tous vos anciens articles comme nouveaux chez les abonnés au flux. Passez skip-columns avec guid quand vous utilisez WP-CLI. La seule exception courante est un site qui n'a jamais eu de flux public ni d'abonnés.

Comment savoir si une valeur est cassée après un mauvais remplacement ?

Les valeurs sérialisées cassées paraissent normales dans la base mais reviennent vides dans WordPress. Les widgets disparaissent, les réglages de l'outil de personnalisation sont réinitialisés, et les options d'extension reviennent à leurs valeurs par défaut sans le moindre message d'erreur. Activer WP_DEBUG et WP_DEBUG_LOG fait remonter la notice de désérialisation. Comparer la longueur déclarée au nombre réel d'octets le confirme directement.