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

Extensions

Un petit bus d’événements internes pour découpler deux modules d’extension

Sur une extension volumineuse, un événement domaine maison limite les dépendances croisées entre modules sans passer par les hooks globaux de WordPress.

Par WordPress Développement • 8 août 2024 • 4 min de lecture • Aucun commentaire
Un petit bus d'événements internes pour découpler deux modules d'extension

Une extension de gestion d’adhésions associatives, développée sur plus de deux ans par une équipe qui a changé progressivement de composition, avait fini par accumuler six modules internes : gestion des membres, cotisations, événements internes à l’association, communication par e-mail, export comptable et statistiques. Chacun avait initialement été pensé comme indépendant, mais au fil des évolutions, des appels directs s’étaient glissés d’un module à l’autre : le module de cotisations appelait directement une méthode du module de communication pour envoyer un reçu, qui lui-même appelait une méthode du module de statistiques pour mettre à jour un compteur.

Le résultat, prévisible avec le recul mais rarement anticipé en cours de projet : modifier la signature d’une méthode du module de communication cassait silencieusement le module de cotisations, sans qu’aucun lien évident dans l’arborescence des fichiers ne signale cette dépendance à un développeur qui découvrait le code.

Le piège du couplage direct entre modules internes

Un appel direct entre deux classes de modules différents, du type Communication::envoyerRecu( $cotisation ) appelé depuis le module de cotisations, crée une dépendance de compilation immédiate : le module de cotisations ne peut plus être testé, chargé ou modifié sans connaître l’existence et la signature exacte du module de communication. Multiplié par six modules et plusieurs dizaines de points d’appel croisés accumulés sur deux ans, ce couplage rend toute modification risquée, y compris pour un changement en apparence isolé.

Les hooks globaux de WordPress (do_action(), apply_filters()) existent précisément pour permettre à du code externe de s’accrocher à un événement, mais leur usage entre modules internes d’une même extension pose un autre problème : ils sont publics par nature, visibles et potentiellement détournés par n’importe quelle autre extension du site, ce qui n’est pas souhaitable pour une communication strictement interne entre les propres modules d’une extension.

Un bus d’événements interne, privé à l’extension

L'essentiel à retenir : Un module ne doit jamais appeler directement une méthode d'un autre module ; Un bus interne limite le nombre de points de couplage entre modules ; Différent des hooks globaux, réservés à l'extensibilité pour des tiers

La solution retenue introduit une classe simple, un registre d’écouteurs privé à l’extension, distinct du système de hooks global de WordPress :

final class BusEvenementsInterne
{
    private array $ecouteurs = [];

    public function ecouter( string $evenement, callable $callback ): void
    {
        $this->ecouteurs[ $evenement ][] = $callback;
    }

    public function emettre( string $evenement, mixed $donnee ): void
    {
        foreach ( $this->ecouteurs[ $evenement ] ?? [] as $callback ) {
            $callback( $donnee );
        }
    }
}

Le module de cotisations n’appelle désormais plus directement le module de communication : il émet un événement métier, sans savoir qui l’écoute ni combien d’écouteurs y répondent.

// Dans le module Cotisations, sans référence directe à Communication
$bus->emettre( 'cotisation.enregistree', $cotisation );

// Dans le module Communication, enregistré au démarrage de l'extension
$bus->ecouter( 'cotisation.enregistree', function ( Cotisation $cotisation ) {
    Communication::envoyerRecu( $cotisation );
} );

Ce que ce bus change concrètement

Avant (appel direct)Après (bus d’événements)
Le module de cotisations connaît la classe CommunicationLe module de cotisations ne connaît que le bus
Ajouter un traitement supplémentaire modifie le code existantAjouter un traitement supplémentaire ajoute un écouteur, sans toucher au code émetteur
Tester un module isolément nécessite de simuler tous les modules appelésTester un module isolément nécessite seulement de simuler le bus

Un bénéfice concret est apparu rapidement après cette refactorisation : ajouter un septième module, dédié à la génération automatique d’un justificatif fiscal à chaque cotisation enregistrée, ne demandait plus qu’un nouvel écouteur sur l’événement existant cotisation.enregistree, sans toucher au code du module de cotisations lui-même.

Limites de ce motif

  • Un bus interne complexifie légèrement le suivi du flux d’exécution : retrouver qui écoute un événement donné demande de chercher dans le code, contrairement à un appel direct visible immédiatement.
  • Il ne remplace pas les hooks publics de l’extension, destinés aux développeurs tiers qui souhaitent l’étendre : ce bus reste une affaire strictement interne aux modules de l’extension elle-même.
  • Sur une petite extension à deux ou trois modules, ce motif ajoute une complexité que le couplage direct ne justifie pas encore de retirer.

Le repère que je retiens pour introduire ce motif : dès qu’un module commence à appeler plus de deux autres modules directement, le coût du bus d’événements devient inférieur au coût du couplage qu’il remplace.

En résumé

Un bus d’événements interne, séparé des hooks publics de WordPress, limite les dépendances croisées entre modules d’une même extension volumineuse, au prix d’un léger effort supplémentaire pour suivre le flux d’exécution. Cet article ne traite pas des hooks publics de l’extension destinés aux développeurs tiers, qui relèvent d’une conception distincte.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Partager :

À propos de l'auteur

WordPress Développement

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

Voir tous ses articles

Dans la même veine

À lire aussi