Le WordPress d'aujourd'hui, décodé pour les développeurs

E-commerce

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.

Par Clément Hadrot • 3 octobre 2024 • 6 min de lecture • Aucun commentaire
Fatal error sur wc_get_container() après une mise à jour WooCommerce

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.

Partager :

À propos de l'auteur

Clément Hadrot

Développeur WordPress, passionné par Elementor, le FSE et l’automatisation par IA.

Voir tous ses articles

Dans la même veine

À lire aussi