Une extension de support client stockait les messages des utilisateurs dans une table personnalisée, créée via dbDelta() à l’activation. Un jour, un client a signalé que certains de ses tickets s’arrêtaient net au milieu d’une phrase, systématiquement après un emoji. Aucune erreur PHP, aucune ligne suspecte dans les journaux serveur, juste du texte manquant, comme sectionné à la volée.
Ce genre de bug déroute parce qu’il ne se manifeste jamais lors des tests habituels : les développeurs écrivent rarement des jeux de test avec des emoji, et le symptôme n’apparaît qu’en production, souvent bien après la mise en ligne de l’extension. La cause, une fois identifiée, est pourtant d’une simplicité presque frustrante.
Le symptôme en détail
En reproduisant le cas avec un message de test contenant un emoji au milieu d’une phrase, l’enregistrement en base s’arrêtait effectivement pile avant le caractère fautif. Aucune exception SQL n’était levée par défaut, MySQL se contentant de tronquer silencieusement la chaîne, sauf en mode strict, où l’insertion aurait échoué avec une erreur explicite du type Incorrect string value.
La cause : une table créée sans le bon charset
La fonction d’activation de l’extension contenait ce code, en apparence anodin :
global $wpdb;
$table_name = $wpdb->prefix . 'support_messages';
$charset_collate = 'DEFAULT CHARACTER SET utf8'; // absence du bon charset
$sql = "CREATE TABLE $table_name (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
message TEXT NOT NULL,
created_at DATETIME NOT NULL,
PRIMARY KEY (id)
) $charset_collate;";
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
dbDelta( $sql );
La table utilisait le jeu de caractères utf8 historique de MySQL, qui ne code chaque caractère que sur trois octets au maximum. Or un emoji, comme la plupart des caractères du plan Unicode supplémentaire, nécessite quatre octets pour être représenté. Le jeu utf8mb4, disponible depuis MySQL 5.5.3, est le seul des deux à supporter réellement ces caractères. WordPress lui-même utilise utf8mb4 pour ses propres tables depuis la version 4.2, sortie en 2015, mais cette configuration ne se propage jamais automatiquement aux tables créées par une extension tierce.
Pourquoi dbDelta ne protège pas de cette erreur
Beaucoup de développeurs pensent, à tort, que dbDelta() applique automatiquement le bon charset. En réalité, cette fonction se contente d’exécuter le CREATE TABLE ou l’ALTER TABLE fourni tel quel, en ajustant surtout la structure des colonnes et des index par rapport à l’existant. Le charset appliqué est exactement celui écrit dans la requête SQL fournie par le développeur, rien de plus.

La bonne pratique consiste à récupérer dynamiquement le charset et la collation utilisés par les tables natives de WordPress, plutôt que de l’écrire en dur, en s’appuyant sur la propriété prévue à cet effet :
global $wpdb;
$table_name = $wpdb->prefix . 'support_messages';
$charset_collate = $wpdb->get_charset_collate(); // s'aligne sur la config réelle du site
$sql = "CREATE TABLE $table_name (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
message TEXT NOT NULL,
created_at DATETIME NOT NULL,
PRIMARY KEY (id)
) $charset_collate;";
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
dbDelta( $sql );
La méthode $wpdb->get_charset_collate() renvoie le charset et la collation réellement configurés pour l’installation WordPress en cours, ce qui garantit une cohérence avec les tables natives, y compris sur des hébergements où l’administrateur a imposé une collation particulière.
Corriger une table déjà en production
Changer le code d’activation ne suffit pas si la table existe déjà avec le mauvais charset : il faut la convertir, en acceptant qu’une conversion de charset descendant vers montant ne récupère jamais les données déjà tronquées, seulement celles encore stockées correctement.
wp db query "ALTER TABLE wp_support_messages
CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;"
Sur une table volumineuse, cette conversion peut verrouiller la table pendant sa durée et doit donc s’exécuter en dehors des heures de forte affluence, idéalement précédée d’une sauvegarde complète de la base.
Vérifier après correction
- Contrôler le charset réel d’une table existante avec
wp db query "SHOW CREATE TABLE wp_support_messages;", qui affiche la définition complète, charset inclus. - Tester l’insertion d’un message contenant volontairement plusieurs emoji consécutifs, pas un seul, certains bugs de troncature ne se révélant qu’à partir du second caractère problématique.
- Vérifier également la collation, pas seulement le charset : une incohérence entre
utf8mb4_general_cietutf8mb4_unicode_520_cisur des tables jointes peut provoquer des erreurs de comparaison silencieuses.
Un réflexe qui nous a évité ce piège sur les projets suivants : ne jamais écrire un charset en dur dans une requête
CREATE TABLEd’extension, toujours passer par$wpdb->get_charset_collate(), même pour un prototype qui semble n’être testé qu’en interne.
En résumé
Ce bug n’a rien d’exotique : il touche potentiellement toute extension qui crée sa propre table sans s’appuyer sur les réglages du site. Le correctif ne demande qu’une ligne de code différente, mais son absence peut faire perdre silencieusement des données pendant des mois avant que le symptôme ne soit signalé, souvent par un client peu technique qui ne saura décrire le problème qu’en disant que « le texte est coupé ».