vendredi 25 septembre 2026

À propos

Contact

Extensions

Architecture d’une extension WordPress moderne avec namespaces et PSR-4

Sortir du préfixage à la main et du grand fichier plugin.php pour adopter les namespaces PHP et l'autoload PSR-4, sans complexifier inutilement une petite extension.

Par Clément Hadrot • 11 février 2020 • 6 min de lecture • Aucun commentaire
Architecture d'une extension WordPress moderne avec namespaces et PSR-4

Ouvrez le fichier principal d’une extension WordPress écrite il y a cinq ans et il y a de fortes chances que vous tombiez sur un mur de fonctions préfixées mon_plugin_, toutes déclarées dans le même fichier de deux mille lignes. Ce n’est pas de la mauvaise volonté : c’est simplement l’héritage d’une époque où PHP 5.2 ne supportait pas les namespaces et où WordPress lui-même vivait sans autoload digne de ce nom. En 2020, avec PHP 7.4 largement disponible chez les hébergeurs et WordPress qui exige PHP 5.6.20 au minimum mais recommande 7.4, il n’y a plus aucune raison de continuer ainsi.

Cet article propose une architecture concrète pour une extension neuve ou en cours de refonte : organisation des dossiers, namespace racine, autoload PSR-4 avec ou sans Composer, et quelques pièges spécifiques à WordPress qui ne se posent pas dans un projet Symfony ou Laravel classique.

Pourquoi les namespaces changent tout

Le problème historique de PHP en contexte WordPress est simple : toutes les fonctions et classes déclarées dans le scope global entrent en collision si deux extensions choisissent le même nom. La solution de la communauté a longtemps été le préfixage manuel : acme_get_settings(), ACME_Settings, acme-settings-page. Cela fonctionne, mais alourdit chaque ligne de code et n’empêche pas complètement les collisions si deux développeurs choisissent le même préfixe court.

Un namespace PHP résout le problème à la racine. Au lieu de :

function acme_settings_get_option( $key ) {
    return get_option( 'acme_settings_' . $key );
}

on écrit :

namespace Acme\Settings;

function get_option_value( string $key ) {
    return \get_option( 'acme_settings_' . $key );
}

Notez le \get_option avec un antislash initial : depuis un namespace, les fonctions natives de PHP et les fonctions globales de WordPress doivent être appelées avec ce préfixe, sous peine que PHP cherche d’abord une fonction Acme\Settings\get_option qui n’existe pas. C’est le piège numéro un des développeurs qui migrent une extension vers les namespaces : un oubli d’antislash sur wp_die, add_action ou sanitize_text_field provoque une fatale error immédiate.

Choisir un namespace racine et une arborescence

Convention largement adoptée : un vendor namespace correspondant à l’auteur ou l’agence, suivi du nom de l’extension.

  • Acme\MonExtension\ comme racine
  • Acme\MonExtension\Admin\ pour tout ce qui touche à l’interface d’administration
  • Acme\MonExtension\Frontend\ pour l’affichage public
  • Acme\MonExtension\Rest\ pour les contrôleurs REST
  • Acme\MonExtension\Cron\ pour les tâches planifiées

Côté fichiers, la structure suit le namespace :

mon-extension/
  mon-extension.php
  composer.json
  src/
    Admin/
      SettingsPage.php
    Frontend/
      Shortcode.php
    Rest/
      ProductsController.php
    Plugin.php
  languages/
  uninstall.php
L'essentiel à retenir : Namespace = fin des préfixes à rallonge ; Autoload PSR-4 = zéro require manuel ; Composer facultatif, autoloader maison possible

Autoload PSR-4 avec Composer

La méthode la plus robuste reste Composer, même pour une extension qui n’a par ailleurs aucune dépendance externe. Un composer.json minimal :

{
    "name": "acme/mon-extension",
    "autoload": {
        "psr-4": {
            "Acme\\MonExtension\\": "src/"
        }
    }
}

La commande composer dump-autoload -o génère un fichier vendor/autoload.php optimisé qu’il suffit de charger en haut du fichier principal :

require_once __DIR__ . '/vendor/autoload.php';

use Acme\MonExtension\Plugin;

add_action( 'plugins_loaded', [ Plugin::class, 'instance' ] );

La correspondance PSR-4 est mécanique : la classe Acme\MonExtension\Admin\SettingsPage doit se trouver dans src/Admin/SettingsPage.php. Le moindre écart de casse dans le nom de fichier casse l’autoload sur un serveur Linux (sensible à la casse), alors que cela passera inaperçu en local sous macOS ou Windows. C’est une source classique de bug qui ne se révèle qu’en production.

Se passer de Composer : un autoloader maison

Toutes les extensions distribuées sur WordPress.org n’embarquent pas de dossier vendor, en partie pour limiter le poids du zip et éviter les conflits de version entre extensions qui chargeraient chacune leur propre Composer autoload. Un autoloader PSR-4 maison tient en une dizaine de lignes :

spl_autoload_register( function ( $class ) {
    $prefix = 'Acme\\MonExtension\\';
    if ( strpos( $class, $prefix ) !== 0 ) {
        return;
    }
    $relative = substr( $class, strlen( $prefix ) );
    $file     = __DIR__ . '/src/' . str_replace( '\\', '/', $relative ) . '.php';
    if ( file_exists( $file ) ) {
        require $file;
    }
} );

Cette solution suffit largement pour une extension qui ne dépend d’aucune bibliothèque tierce. Dès qu’une dépendance externe entre en jeu (un client HTTP, une bibliothèque de logs), Composer redevient pertinent, quitte à préfixer les dépendances avec un outil comme Mozart pour éviter les collisions de version avec d’autres extensions du même site.

Sur nos projets, la bascule d’un plugin monolithique vers une architecture en classes namespacées a systématiquement réduit de moitié le temps passé à localiser un bug : on sait immédiatement dans quel dossier chercher selon la nature du problème.

Une classe Plugin comme point d’entrée unique

Plutôt que de disperser les appels à add_action dans tout le code, une classe centrale orchestre le chargement :

namespace Acme\MonExtension;

class Plugin {
    private static ?Plugin $instance = null;

    public static function instance(): Plugin {
        if ( null === self::$instance ) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    private function __construct() {
        ( new Admin\SettingsPage() )->register();
        ( new Frontend\Shortcode() )->register();
        ( new Rest\ProductsController() )->register();
    }
}

Chaque classe expose une méthode register() qui contient ses propres add_action et add_filter. Cette convention, simple mais disciplinée, rend le code prévisible : n’importe quel développeur qui rejoint le projet sait qu’il trouvera les hooks d’une fonctionnalité dans la classe qui la représente, jamais éparpillés ailleurs.

Éviter les pièges spécifiques à WordPress

Trois points méritent une attention particulière lors de cette migration :

  • Le nom du fichier principal de l’extension (celui qui porte l’en-tête Plugin Name) ne peut pas être namespacé : WordPress le charge par simple include, donc gardez-le minimal et laissez-le déléguer tout de suite à la classe Plugin.
  • Les hooks qui attendent un callback sous forme de chaîne de caractères (rares en 2020 mais encore présents dans certaines extensions tierces) ne comprennent pas la syntaxe Namespace\Classe::methode telle quelle : préférez toujours un tableau [ $instance, 'methode' ] ou une closure.
  • Les fonctions de template exposées aux thèmes (via do_action avec des arguments destinés à un template PHP classique) doivent rester des fonctions globales si vous voulez qu’un intégrateur puisse les appeler facilement depuis functions.php, sans avoir à écrire un use statement.

En résumé

Passer aux namespaces et à l’autoload PSR-4 n’est pas un exercice de style : c’est ce qui permet à une extension de grossir sans devenir illisible. Pour une extension neuve, adoptez cette architecture dès le premier commit, le coût est nul. Pour une extension existante, la migration peut se faire progressivement, classe par classe, en gardant les anciennes fonctions préfixées comme de simples enveloppes qui appellent le nouveau code le temps de la transition.

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