La fonctionnalité semblait anodine : permettre à un client de personnaliser un tee-shirt (couleur, taille, texte imprimé) et retrouver cette personnalisation en cours de session, même après avoir navigué sur plusieurs pages avant de finaliser son choix. Le développeur d’origine avait stocké, directement dans la session WooCommerce, un objet Personnalisation_Produit maison, contenant entre autres une référence vers l’objet WC_Product complet du produit concerné.
Quelques mois plus tard, une évolution du code a renommé une propriété interne de cette classe. Le lendemain de la mise en production, plusieurs clients ont signalé des paniers vidés sans raison apparente, ou pire, des erreurs serveur au chargement de la page panier.
Ce qu’on voit
Le journal d’erreurs PHP affichait des avertissements de désérialisation, du type PHP Warning: Erreur lors de la désérialisation d'une valeur, suivis d’une exception fatale sur certaines requêtes touchant au panier. Le comportement était intermittent : seuls les clients ayant entamé une session avant la mise en production, avec un objet Personnalisation_Produit déjà sérialisé dans leur session selon l’ancienne structure de classe, rencontraient le problème.
Pourquoi c’est un problème
La session WooCommerce, gérée par la classe WC_Session_Handler, stocke ses données sous forme sérialisée, le plus souvent dans la table wp_woocommerce_sessions, indexée par un identifiant de client. Cette sérialisation fonctionne parfaitement pour des données scalaires ou des tableaux simples, mais devient fragile dès qu’elle porte sur des objets PHP complets : PHP sérialise alors le nom complet de la classe avec l’état de chacune de ses propriétés, et la désérialisation échoue, silencieusement ou fatalement selon les cas, si cette classe a changé de structure entre le moment de l’écriture et celui de la lecture.

À cela s’ajoute un second problème, moins spectaculaire mais tout aussi réel : un objet WC_Product complet embarque potentiellement une quantité de données bien supérieure à ce que la personnalisation nécessite réellement, alourdissant chaque session stockée et chaque lecture de session à chaque requête, y compris pour les visiteurs qui n’utilisent jamais la fonctionnalité de personnalisation.
Un risque de sécurité rarement mentionné
Stocker des objets sérialisés dont la structure peut évoluer ouvre aussi la porte, dans des scénarios plus larges où une donnée externe non maîtrisée entrerait dans ce mécanisme, à des attaques par injection d’objet PHP. Ce n’était pas le vecteur exploité ici, la donnée restant interne au site, mais c’est une raison supplémentaire, documentée de longue date dans l’écosystème PHP, d’éviter la sérialisation d’objets complexes dès que leur origine ou leur stabilité structurelle n’est pas garantie dans le temps.
Quoi faire à la place
La correction a consisté à ne conserver en session que les données strictement nécessaires à la personnalisation, sous forme de tableau associatif simple, en recalculant à la volée les informations produit à partir de son identifiant plutôt que de conserver l’objet lui-même :
// À éviter : un objet complexe stocké tel quel
WC()->session->set( 'perso_produit', $personnalisation_objet );
// Version robuste : uniquement des données scalaires
WC()->session->set( 'perso_produit', array(
'product_id' => $product->get_id(),
'couleur' => sanitize_text_field( $couleur ),
'taille' => sanitize_text_field( $taille ),
'texte' => sanitize_text_field( $texte ),
) );
// À la lecture, on recharge le produit à la demande
$donnees = WC()->session->get( 'perso_produit' );
$produit = wc_get_product( $donnees['product_id'] );
Cette approche présente un avantage supplémentaire, indépendant de l’incident initial : elle reste valide même si la fiche produit change entre-temps, puisque le produit est rechargé à jour à chaque lecture plutôt que figé dans son état au moment de la sauvegarde en session.
- Ne jamais stocker en session un objet métier complet quand un identifiant suffit à le reconstruire.
- Limiter la session aux données réellement nécessaires à la restitution de l’expérience, pas à un instantané complet de l’état applicatif.
- Prévoir, pour toute évolution de structure de données déjà en session chez des clients actifs, une valeur par défaut robuste en cas d’échec de lecture plutôt qu’une erreur fatale non interceptée.
La session n’est pas une base de données : elle mérite d’être traitée comme un espace volatile, tolérant à la perte, jamais comme le seul endroit où vit une information dont la structure est amenée à évoluer.
En résumé
Le bug n’est apparu qu’au moment d’un changement de structure de classe, ce qui explique pourquoi il n’avait jamais été détecté en test : un test classique recharge rarement une session ancienne créée avec une version antérieure du code. Ce cas illustre une règle plus générale, valable bien au-delà de WooCommerce : tout ce qui est sérialisé pour être relu plus tard mérite une structure aussi simple et stable que possible, et un identifiant plutôt qu’un objet dès que la donnée source peut évoluer indépendamment.