# Fatal error sur wc_get_container() après une mise à jour WooCommerce

> Une extension maison plante après une montée de version WooCommerce : le conteneur de services interne a changé d'accès. Diagnostic et correctif ligne par ligne.

- Auteur : Clément Hadrot
- Publié le : 2024-10-03
- Mis à jour le : 2024-10-03
- Catégorie : E-commerce
- URL : https://wpmoderne.dev.wordpress-developpement.fr/ecommerce/fatal-error-wc-get-container-mise-a-jour-woocommerce/

## L’essentiel

- wc_get_container() n'est disponible qu'à partir d'un hook précis
- L'appel direct trop tôt casse l'activation du plugin
- Le correctif tient en un changement de hook

`PHP Fatal error: Uncaught Error: Call to undefined function wc_get_container()`. C'est le message qui attend un client trois minutes après avoir cliqué sur « Mettre à jour » dans l'administration WordPress. La boutique affiche une page blanche, le tableau de bord est inaccessible, et l'extension interne développée deux ans plus tôt pour piloter des remises fournisseurs refuse de charger.

Ce cas est révélateur d'un problème plus large : les extensions qui accèdent directement à des mécanismes internes de WooCommerce sans respecter l'ordre de chargement des hooks. Le conteneur de services (`wc_get_container()`) existe depuis plusieurs versions, mais son point d'entrée a été déplacé au fil des mises à jour, et un code qui fonctionnait « par chance » finit par casser au premier changement d'ordre d'initialisation.

## Symptôme : une extension qui ne charge plus après la mise à jour

Le scénario est toujours le même. Avant la mise à jour, tout fonctionne. Juste après, le site affiche un écran blanc ou, si `WP_DEBUG` est actif, l'erreur suivante dans les logs :

```
PHP Fatal error:  Uncaught Error: Call to undefined function wc_get_container() in /wp-content/plugins/remises-fournisseurs/remises-fournisseurs.php:14
Stack trace:
#0 /wp-content/plugins/remises-fournisseurs/remises-fournisseurs.php(6): Remises_Fournisseurs::init()
#1 /wp-includes/class-wp-hook.php(324): Remises_Fournisseurs::{closure}('')
#2 /wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#3 /wp-content/plugins/remises-fournisseurs/remises-fournisseurs.php(4): do_action('plugins_loaded')
#4 /wp-settings.php(535): include_once('...')
#5 /wp-config.php(112): require_once('...')
#6 /wp-load.php(50): require_once('...')
#7 /wp-blog-header.php(13): require('...')
#8 index.php(17): require('...')
#9 {main}
  thrown in /wp-content/plugins/remises-fournisseurs/remises-fournisseurs.php on line 14
```

Le plugin appelle `wc_get_container()` directement sur `plugins_loaded`, avant que WooCommerce lui-même n'ait terminé de s'initialiser. Le fait que cela fonctionnait auparavant tenait à l'ordre alphabétique de chargement des plugins, pas à une garantie du cœur.

## Diagnostic : comprendre le conteneur de services de WooCommerce

> L'essentiel à retenir : wc_get_container() n'est disponible qu'à partir d'un hook précis ; L'appel direct trop tôt casse l'activation du plugin ; Le correctif tient en un changement de hook

Depuis l'introduction progressive d'une architecture orientée services, WooCommerce expose un conteneur d'injection de dépendances conforme à l'interface PSR-11, accessible via la fonction globale `wc_get_container()`. Ce conteneur héberge des classes internes comme `Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableDataStore` ou les services liés au stockage haute performance des commandes (HPOS).

Le piège, c'est que cette fonction n'existe que lorsque le fichier principal de WooCommerce a été chargé et que sa classe `WooCommerce` a exécuté son initialisation. Sur `plugins_loaded`, l'ordre d'exécution entre plugins dépend de la priorité du hook et, à priorité égale, de l'ordre alphabétique des noms de dossiers. Un plugin nommé `remises-fournisseurs` se charge avant `woocommerce` dans l'ordre alphabétique strict, ce qui explique pourquoi l'appel plantait déjà avant la mise à jour dans certains environnements, et pourquoi un simple changement de version (qui a réordonné légèrement l'initialisation interne) l'a fait apparaître ailleurs.

Pour vérifier l'hypothèse, on ajoute un test défensif temporaire :

```
add_action( 'plugins_loaded', function() {
    if ( ! function_exists( 'wc_get_container' ) ) {
        error_log( 'wc_get_container indisponible au moment de plugins_loaded' );
        return;
    }
    // suite du code
} );
```

Le log confirme immédiatement le diagnostic : la fonction n'est pas encore déclarée à ce stade sur ce site précis.

## Correctif : reporter l'appel après l'initialisation complète

La solution ne consiste pas à retarder artificiellement l'exécution avec un `sleep` ou une boucle d'attente, mais à s'accrocher au bon hook. WooCommerce déclenche l'action `woocommerce_init` une fois que sa classe principale a terminé sa propre initialisation, conteneur de services compris. C'est le point d'entrée fiable :

```
add_action( 'woocommerce_init', 'remises_fournisseurs_bootstrap' );

function remises_fournisseurs_bootstrap() {
    if ( ! function_exists( 'wc_get_container' ) ) {
        return;
    }

    $container = wc_get_container();
    // Récupération d'un service interne, exemple pour un contrôle de compatibilité
    if ( $container->has( \Automattic\WooCommerce\Internal\Features\FeaturesController::class ) ) {
        $features = $container->get( \Automattic\WooCommerce\Internal\Features\FeaturesController::class );
        // logique métier ici
    }
}
```

Deux détails comptent ici :

- Le test `function_exists()` reste une garde de sécurité, même sur `woocommerce_init`, pour les sites où WooCommerce est désactivé temporairement.
- La méthode `has()` du conteneur évite un `Error` supplémentaire si la classe interne visée a été renommée ou déplacée dans une future version.

Une fois ce changement déployé, le message d'erreur disparaît et le plugin s'initialise correctement, quel que soit l'ordre alphabétique des dossiers de plugins.

## Cas particulier : l'activation du plugin lui-même

Un piège voisin touche le hook d'activation. Si le code d'activation (`register_activation_hook`) tente lui aussi d'appeler `wc_get_container()`, il échoue systématiquement, car les hooks d'activation s'exécutent avant même que `plugins_loaded` ne soit déclenché pour les autres extensions. Dans ce cas, il faut reporter la logique dépendante du conteneur à une action différée :

```
register_activation_hook( __FILE__, function() {
    // Ne PAS appeler wc_get_container() ici.
    update_option( 'remises_fournisseurs_activation_pending', true );
} );

add_action( 'woocommerce_init', function() {
    if ( get_option( 'remises_fournisseurs_activation_pending' ) ) {
        delete_option( 'remises_fournisseurs_activation_pending' );
        // logique d'activation dépendante du conteneur
    }
} );
```

Ce schéma « drapeau différé » évite de dupliquer la logique métier et garantit qu'elle s'exécute une fois l'environnement WooCommerce pleinement disponible.

## Prévention : verrouiller les dépendances entre extensions

Trois pratiques limitent le risque de retrouver ce type de crash à chaque montée de version :

1. Déclarer la dépendance à WooCommerce via l'en-tête `Requires Plugins: woocommerce` dans le fichier principal du plugin, disponible depuis WordPress 6.5, ce qui empêche l'activation si WooCommerce est absent ou désactivé.
2. Toujours s'accrocher à `woocommerce_init` (ou, pour un simple test de présence, à `woocommerce_loaded`) plutôt qu'à `plugins_loaded` pour tout code interagissant avec les classes internes du cœur.
3. Ajouter un test automatisé, même minimal, qui installe une version mineure supérieure de WooCommerce dans un environnement de recette avant chaque déploiement, afin de repérer ces régressions avant la production plutôt qu'après.

> Sur nos projets, tout code touchant à une classe interne de WooCommerce (préfixée `Automattic\WooCommerce\Internal`) est systématiquement isolé dans une classe dédiée, chargée uniquement sur `woocommerce_init`. Cela a éliminé ce type d'incident sur l'ensemble des sites que nous maintenons depuis deux ans.

## En résumé

Un `wc_get_container()` introuvable n'est presque jamais un bug du cœur WooCommerce : c'est un appel exécuté trop tôt dans le cycle de chargement de WordPress. Le correctif est simple une fois le diagnostic posé : déplacer l'appel vers `woocommerce_init`, sécuriser l'activation avec un drapeau différé, et déclarer explicitement la dépendance au plugin dans l'en-tête. Ces trois réflexes suffisent à traverser les futures montées de version sans mauvaise surprise pour les clients qui dépendent de l'extension.
