# HPOS de WooCommerce : rendre votre extension compatible avec les commandes

> WooCommerce migre le stockage des commandes vers des tables dédiées. Déclarez la compatibilité, remplacez les accès directs aux post meta et testez les deux modes.

- Auteur : Clément Hadrot
- Publié le : 2022-12-15
- Mis à jour le : 2022-12-15
- Catégorie : Extensions
- URL : https://wpmoderne.dev.wordpress-developpement.fr/extensions/hpos-woocommerce-extension-compatible-commandes/

## L’essentiel

- Déclarer FEATURES_PLUGIN via before_woocommerce_init
- Remplacer get_post_meta par les API CRUD
- Tester en mode legacy et en mode HPOS

Une cliente qui gère une boutique de pièces détachées nous a contactés fin novembre parce qu'une extension de facturation qu'elle utilisait affichait des montants à zéro depuis sa dernière mise à jour de WooCommerce. Le coupable : un accès direct à `get_post_meta()` sur l'ID de commande, alors que WooCommerce venait d'activer le stockage haute performance des commandes sur son site. L'extension lisait une table qui n'était plus celle où vivaient les données.

High-Performance Order Storage (HPOS), aussi appelé « Custom Order Tables », change la façon dont WooCommerce range les commandes en base. Au lieu de post types stockés dans `wp_posts` et `wp_postmeta`, les commandes vivent dans des tables dédiées : `wc_orders`, `wc_order_operational_data`, `wc_orders_meta`, entre autres. Si votre extension touche aux commandes, il faut déclarer sa compatibilité et corriger les accès qui contournent l'API.

## Comprendre ce qui change réellement

HPOS n'est pas une simple option cosmétique. Tant que le mode legacy reste actif, une commande est un objet `WC_Order` adossé à un article de type `shop_order`. Une fois HPOS activé, l'objet `WC_Order` existe toujours, avec les mêmes méthodes publiques, mais son stockage physique change complètement. Le compatibility layer de WooCommerce peut, pendant une période de transition, synchroniser les deux structures, mais s'appuyer là-dessus dans du code neuf est une mauvaise idée : la synchronisation peut être désactivée par le site.

Concrètement, tout code qui fait l'hypothèse qu'une commande est un article WordPress classique casse tôt ou tard : requêtes `WP_Query` avec `post_type => 'shop_order'`, appels à `get_post_meta( $order_id, ... )`, jointures SQL directes sur `wp_posts`.

## Déclarer la compatibilité

WooCommerce fournit un hook dédié pour annoncer qu'une extension gère correctement HPOS. Il se déclenche très tôt, avant l'initialisation de WooCommerce lui-même :

> L'essentiel à retenir : Déclarer FEATURES_PLUGIN via before_woocommerce_init ; Remplacer get_post_meta par les API CRUD ; Tester en mode legacy et en mode HPOS

```
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
        );
    }
} );
```

Cette déclaration est visible dans l'écran **WooCommerce → Réglages → Fonctionnalités → Compatibilité des extensions**. Sans elle, WooCommerce affiche votre extension comme « non testée » et peut, selon la configuration, empêcher l'activation de HPOS tant que des extensions incompatibles sont actives.

## Remplacer les accès directs par les API CRUD

Le principe central : ne jamais parler à la base directement, toujours passer par l'objet `WC_Order` ou par les fonctions de façade de WooCommerce. Quelques correspondances utiles :

- `get_post_meta( $order_id, '_billing_vat', true )` devient `$order->get_meta( '_billing_vat', true )`.
- `update_post_meta( $order_id, '_billing_vat', $valeur )` devient `$order->update_meta_data( '_billing_vat', $valeur ); $order->save();`.
- `get_post( $order_id )->post_status` devient `$order->get_status()`.
- Une recherche par identifiant client passe par `wc_get_orders( array( 'customer_id' => $user_id ) )` plutôt qu'une `WP_Query` sur `shop_order`.

Pour charger une commande sans hypothèse de stockage, la fonction à retenir est `wc_get_order( $order_id )`, qui renvoie toujours l'objet correct quel que soit le mode actif. Si votre extension fait des requêtes SQL personnalisées pour des besoins de reporting, il faut détecter le mode actif via `\Automattic\WooCommerce\Utilities\OrderUtil::custom_orders_table_usage_is_enabled()` et adapter la requête, ou mieux, utiliser `wc_get_orders()` avec ses filtres avancés qui couvrent la grande majorité des cas.

### Le cas des metabox et des colonnes personnalisées

Si votre extension ajoute une colonne dans la liste des commandes ou une metabox dans l'écran d'édition, elle doit s'accrocher aux hooks HPOS plutôt qu'aux hooks historiques liés à l'écran `edit.php`. WooCommerce expose des filtres comme `woocommerce_shop_order_list_table_columns` et `woocommerce_shop_order_list_table_custom_column`, qui fonctionnent dans les deux modes, contrairement aux hooks `manage_shop_order_posts_columns` qui ne s'appliquent qu'au mode legacy.

## Tester les deux modes avant publication

WooCommerce permet d'activer HPOS en synchronisation avec le stockage classique, ce qui donne un terrain de test confortable : **WooCommerce → Réglages → Fonctionnalités**, section « Stockage des commandes à haute performance ». Notre méthode pour valider une extension :

1. Créer un jeu de commandes de test avec des statuts variés (en attente, en cours, terminée, remboursée).
2. Activer HPOS avec synchronisation activée, vérifier que l'extension fonctionne à l'identique.
3. Désactiver la synchronisation, forcer un environnement HPOS pur, et rejouer les mêmes scénarios.
4. Revenir en mode legacy et confirmer qu'aucune régression n'apparaît pour les sites qui n'ont pas encore basculé.

Un piège classique concerne les jointures SQL personnalisées pour des rapports de vente : dans un environnement HPOS pur, les tables `wp_postmeta` ne contiennent plus les métadonnées de commande, une requête qui les cible silencieusement ne remonte rien, sans erreur visible.

> Sur nos projets, la règle qu'on applique systématiquement : si le code touche à une commande, il passe par `WC_Order` ou `wc_get_orders()`, jamais par une fonction post-native. Ça évite 90 % des soucis de compatibilité HPOS avant même d'y penser.

## En résumé

La compatibilité HPOS n'est pas une case à cocher isolée : elle demande de revoir chaque endroit où l'extension suppose qu'une commande est un article WordPress. Déclarez la compatibilité via `FeaturesUtil::declare_compatibility()`, remplacez systématiquement les accès meta directs par l'API CRUD de `WC_Order`, et testez réellement dans les deux modes avant de publier une mise à jour. Le jeu en vaut la chandelle : HPOS devient progressivement la norme sur les boutiques à fort volume, et une extension non déclarée compatible finira par être signalée aux utilisateurs comme un frein à la migration.
