Symptôme : juste après l’activation du stockage haute performance des commandes (HPOS) sur une boutique WooCommerce, un bloc personnalisé affichant le chiffre d’affaires du mois s’est mis à afficher zéro, alors que des commandes continuaient d’arriver normalement. Aucune erreur PHP visible, aucun message dans les journaux : juste un chiffre obstinément resté à zéro.
Ce billet retrace le diagnostic complet de ce bug et le correctif appliqué. La migration WooCommerce elle-même vers HPOS, avec ses étapes de synchronisation, relève d’un autre sujet et n’est pas traitée ici.
Diagnostic : d’où vient réellement le zéro
Le bloc, développé avant l’existence de HPOS, interrogeait directement la table wp_postmeta pour additionner le montant total des commandes du mois, en filtrant sur la méta-donnée _order_total attachée à des articles du type shop_order :
global $wpdb;
$total = $wpdb->get_var( "
SELECT SUM(meta_value)
FROM {$wpdb->postmeta}
WHERE meta_key = '_order_total'
AND post_id IN (
SELECT ID FROM {$wpdb->posts}
WHERE post_type = 'shop_order'
AND post_date >= '2022-11-01'
)
" );
Ce code fonctionnait très bien tant que WooCommerce stockait les commandes comme des articles classiques dans wp_posts et wp_postmeta. Une fois HPOS activé, les nouvelles commandes ne sont plus créées dans ces tables du tout : elles vivent désormais dans des tables dédiées, wp_wc_orders et ses tables associées. La requête SQL du bloc continuait de chercher des commandes dans wp_posts, là où elles n’apparaissaient tout simplement plus.

Correctif : passer par l’API CRUD de commande
La correction ne consiste pas à réécrire la requête SQL pour cibler les nouvelles tables directement, ce qui recréerait la même fragilité en cas de futur changement de schéma. La bonne approche s’appuie sur l’API CRUD de commande fournie par WooCommerce, compatible avec les deux schémas de stockage sans distinction dans le code du bloc :
function calculer_ca_du_mois() {
$commandes = wc_get_orders( array(
'status' => array( 'wc-completed', 'wc-processing' ),
'date_created' => '>=' . strtotime( 'first day of this month' ),
'limit' => -1,
) );
$total = 0;
foreach ( $commandes as $commande ) {
$total += $commande->get_total();
}
return $total;
}
Cette fonction s’appuie sur wc_get_orders() et sur la méthode get_total() de l’objet commande, tous deux garantis par WooCommerce de fonctionner indifféremment que HPOS soit activé ou non. Le bloc n’a plus jamais besoin de savoir où sont physiquement stockées les commandes.
Prévention : comment éviter ce piège à l’avenir
- Ne jamais interroger directement
wp_postsouwp_postmetapour des données de commande WooCommerce - Toujours passer par l’API CRUD fournie (
wc_get_order(),wc_get_orders()) pour toute lecture ou écriture - Déclarer explicitement la compatibilité HPOS de toute extension personnalisée via
FeaturesUtil::declare_compatibility()
add_action( 'before_woocommerce_init', function() {
if ( class_exists( \Automattic\WooCommerce\Utilities\FeaturesUtil::class ) ) {
\Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility(
'custom_order_tables', __FILE__, true
);
}
} );
Un test simple à ne jamais sauter
Sur tout site WooCommerce actif, tester un bloc de statistiques sur un environnement de recette avec HPOS activé avant toute mise en production évite ce genre de surprise. Le comportement en mode compatible (avec synchronisation entre ancien et nouveau schéma) peut masquer temporairement le problème, jusqu’à ce que la synchronisation soit désactivée.
Toute requête SQL qui cible directement les tables internes de WooCommerce mérite d’être considérée comme fragile par construction, quelle que soit l’ancienneté du code : l’API CRUD existe précisément pour absorber ce genre de changement de schéma.
En résumé
Un bloc qui interroge directement les tables internes de WooCommerce cassera tôt ou tard, dès que le schéma de stockage évolue. S’appuyer systématiquement sur l’API CRUD de commande, plutôt que sur des requêtes SQL directes, protège durablement contre ce type de rupture silencieuse.