Est-ce qu’un développeur qui écrit wp_cache_add() à la place de wp_cache_set() introduit vraiment un bug ? La réponse est oui, et ce genre d’erreur est particulièrement vicieux parce qu’elle ne se manifeste pas immédiatement : le code fonctionne parfaitement au premier appel, puis semble ignorer silencieusement toute mise à jour ultérieure de la donnée.
C’est exactement ce qui s’est produit sur une extension maison développée pour un site de recettes de cuisine, qui mettait en cache le nombre d’ingrédients d’une recette pour éviter de le recalculer à chaque affichage. Après une modification de recette, l’ancien nombre continuait à s’afficher pendant plusieurs heures, jusqu’à expiration naturelle du cache.
Le fonctionnement interne des deux fonctions
wp_cache_set( $key, $data, $group, $expire ) écrit systématiquement la valeur fournie dans le cache d’objet, qu’une valeur existe déjà pour cette clé ou non. C’est le comportement attendu pour la grande majorité des usages : on veut que la valeur la plus récente remplace la précédente.
wp_cache_add( $key, $data, $group, $expire ) se comporte différemment : la fonction vérifie d’abord si une valeur existe déjà pour cette clé et ce groupe. Si c’est le cas, elle ne fait rien et retourne false, sans jamais écraser la valeur existante. Ce comportement est voulu et utile dans certains contextes précis, notamment pour éviter qu’une opération concurrente ne réécrive une valeur déjà posée par un autre processus, un usage proche d’un verrou léger.
Le piège rencontré dans le code réel
Le développeur de l’extension avait choisi wp_cache_add() en pensant, par analogie avec update_option() et add_option(), qu’il s’agissait simplement d’une variante plus prudente d’écriture en cache, sans en comprendre la conséquence exacte sur une mise à jour :
function recette_get_nombre_ingredients( $recette_id ) {
$cache_key = 'nb_ingredients_' . $recette_id;
$nombre = wp_cache_get( $cache_key, 'recettes' );
if ( false === $nombre ) {
$nombre = recette_compter_ingredients( $recette_id );
wp_cache_add( $cache_key, $nombre, 'recettes', HOUR_IN_SECONDS );
}
return $nombre;
}
Ce code semble correct à première lecture : wp_cache_get() précède bien l’écriture, et wp_cache_add() n’est appelé qu’en cas d’absence en cache. Le problème apparaît ailleurs, dans la fonction déclenchée à la sauvegarde d’une recette modifiée, qui tentait de forcer la mise à jour immédiate du cache après un changement d’ingrédients :

add_action( 'save_post_recette', function ( $post_id ) {
$nombre = recette_compter_ingredients( $post_id );
wp_cache_add( 'nb_ingredients_' . $post_id, $nombre, 'recettes', HOUR_IN_SECONDS );
} );
Ici, l’usage de wp_cache_add() était une erreur pure : si une valeur existait déjà en cache pour cette clé, ce qui est presque toujours le cas pour une recette déjà consultée, la fonction ne faisait rien du tout. La nouvelle valeur, pourtant calculée correctement, n’était jamais écrite. L’ancienne valeur restait en cache jusqu’à expiration naturelle de l’heure définie.
Le correctif
Le correctif consistait simplement à remplacer wp_cache_add() par wp_cache_set() dans le hook de sauvegarde, où l’intention réelle était bien d’écraser la valeur existante :
add_action( 'save_post_recette', function ( $post_id ) {
$nombre = recette_compter_ingredients( $post_id );
wp_cache_set( 'nb_ingredients_' . $post_id, $nombre, 'recettes', HOUR_IN_SECONDS );
} );
La fonction de lecture, elle, conservait wp_cache_add() à juste titre : dans ce contexte précis, on ne veut écrire que si aucune valeur n’existe déjà, exactement le comportement recherché lors d’une première lecture.
Comment repérer ce type de bug plus tôt
- Se poser systématiquement la question « est-ce que je veux écraser une valeur existante, ou seulement écrire si rien n’existe ? » avant de choisir entre les deux fonctions.
- Ajouter un test unitaire simple qui modifie une donnée puis vérifie que le cache reflète bien la nouvelle valeur immédiatement après.
- Se méfier de toute fonction de mise en cache appelée depuis un hook de sauvegarde ou de mise à jour : c’est précisément le contexte où l’écrasement est presque toujours l’intention recherchée.
En résumé
La différence entre wp_cache_add() et wp_cache_set() tient à un seul comportement, mais ses conséquences en production peuvent rester invisibles pendant des heures, le temps que le cache expire naturellement. Ce cas ne traite pas le choix du backend Redis ou Memcached, qui n’a aucune influence sur ce comportement : la logique d’ajout contre écrasement est définie au niveau de l’API de cache de WordPress elle-même, avant même d’atteindre le backend configuré.