Les hooks WordPress — actions et filtres — constituent le cœur du système d’extension de WordPress. Ils permettent de modifier le comportement du CMS à presque chaque étape de son exécution sans toucher au code du core, ce qui garantit la compatibilité avec les mises à jour futures. Ce guide explore les mécanismes avancés : priorités, closures, objets comme callbacks, hooks conditionnels et patrons de conception pour maintenir un code propre et performant.
Architecture interne du système de hooks WordPress
WordPress maintient en mémoire deux registres globaux : $wp_filter pour les filtres et $wp_actions pour les actions. Ces registres sont des tableaux associatifs indexés par le nom du hook, puis par la priorité (entier, défaut 10), et enfin par les callbacks enregistrés. Quand WordPress appelle do_action() ou apply_filters(), il parcourt ces registres dans l’ordre croissant des priorités et exécute chaque callback dans l’ordre d’enregistrement à priorité égale.
La classe WP_Hook, introduite dans WordPress 4.7, encapsule la logique de gestion des hooks. Chaque entrée du registre est désormais une instance de WP_Hook qui gère les callbacks, la récursivité et les suppressions en cours d’exécution de manière plus robuste. Comprendre cette architecture interne est utile pour déboguer des comportements inattendus comme un callback qui ne s’exécute pas ou une priorité qui ne fonctionne pas comme prévu.
Les hooks peuvent être récursifs : un callback peut ajouter de nouveaux hooks ou se ré-enregistrer lui-même, et WordPress gère correctement ce cas grâce à un compteur de récursivité dans WP_Hook. Cependant, les boucles infinies restent possibles si vous n’implémentez pas de condition d’arrêt. Activez WP_DEBUG et utilisez le plugin Query Monitor pour visualiser les hooks déclenchés et leurs temps d’exécution lors du développement de fonctionnalités complexes.
Différences fondamentales entre actions et filtres
Une action (do_action) signale qu’un événement s’est produit et permet d’y greffer du code supplémentaire. Le callback d’une action ne retourne rien d’utile : son rôle est d’effectuer des effets de bord (envoyer un email, écrire en base, mettre en cache…). Une action peut passer des arguments informatifs à ses callbacks mais ne s’attend pas à recevoir de valeur en retour.
Un filtre (apply_filters) passe une valeur à travers une chaîne de callbacks qui peuvent chacun la transformer avant de la retourner. La valeur finale retournée par le dernier callback est celle utilisée par WordPress. Si votre callback de filtre oublie de retourner la valeur, le résultat est null ou une chaîne vide selon le contexte, ce qui peut causer des bugs silencieux difficiles à tracer.
Une règle pratique : si le hook s’appelle « the_content », « get_the_title » ou « sanitize_email », c’est un filtre — il transforme une donnée. Si le hook s’appelle « save_post », « wp_enqueue_scripts » ou « init », c’est une action — il annonce une étape. Pour les hooks que vous créez dans vos plugins ou thèmes, choisissez des noms explicites avec un préfixe unique (ex. myplugin_filter_price) pour éviter les collisions avec d’autres plugins.
Maîtriser les priorités pour contrôler l’ordre d’exécution
La priorité est un entier passé en troisième argument à add_action() et add_filter(). Les callbacks de priorité inférieure s’exécutent en premier (1 avant 10 avant 100). La valeur par défaut de 10 convient à la plupart des cas, mais dans certains scénarios l’ordre est critique : si vous filtrez le contenu d’un article après que Gutenberg a injecté ses blocs, vous devez utiliser une priorité élevée (20-99) pour vous assurer d’agir après les autres transformations.
Attention aux conflits entre plugins : si deux plugins s’enregistrent sur le même hook avec la même priorité, ils s’exécutent dans l’ordre d’installation de WordPress. Ce comportement n’est pas documenté et peut varier entre versions. Quand vous contrôlez les deux plugins, choisissez des priorités différentes. Quand vous n’en contrôlez qu’un seul, lisez le code de l’autre pour savoir quelle priorité il utilise et positionner le vôtre en conséquence.
Pour les hooks critiques comme wp_head ou wp_footer, des dizaines de callbacks peuvent être enregistrés par WordPress core, les plugins et le thème. La priorité PHP_INT_MAX (2147483647) garantit que votre callback s’exécute en dernier, utile pour les vérifications d’audit. La priorité 1 garantit une exécution en premier. N’abusez pas des valeurs extrêmes : elles rendent le code difficile à déboguer pour les développeurs qui travailleront sur le projet après vous.
Closures et callbacks orientés objet
Les closures PHP (fonctions anonymes) sont pratiques pour les hooks de courte durée ou les callbacks qui capturent des variables de l’environnement. Cependant, elles ont un inconvénient majeur : on ne peut pas les supprimer avec remove_action() ou remove_filter() après leur enregistrement, car PHP ne peut pas comparer deux closures. Réservez les closures aux hooks qui n’ont pas vocation à être surchargés par d’autres plugins ou par le thème enfant.
La syntaxe pour enregistrer une méthode de classe est un tableau : add_action(« init », [$this, « ma_methode »]). Pour une méthode statique : add_action(« init », [« MaClasse », « methode_statique »]). Cette approche permet de supprimer le hook plus tard avec remove_action(« init », [$instance, « ma_methode »]), à condition de passer la même instance d’objet. Un pattern courant est de stocker l’instance dans une propriété statique (singleton) pour pouvoir la récupérer lors de la suppression.
Pour les plugins bien structurés, encapsulez tous vos hooks dans une classe de bootstrap qui enregistre les actions et filtres dans sa méthode __construct() ou une méthode register(). Cette organisation facilite les tests unitaires avec des mocks, la documentation automatique et la maintenance à long terme. Des frameworks comme WP Pusher ou des starters comme WP Plugin Boilerplate adoptent ce pattern et constituent d’excellents exemples à étudier.
<?php
// Exemple : hook avance avec classe et gestion de priorite
class Mon_Plugin_Hooks {
private static $instance = null;
public static function get_instance() {
if ( null === self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
public function register() {
// Action avec priorite personnalisee
add_action( 'save_post', [ $this, 'on_save_post' ], 20, 3 );
// Filtre sur le contenu
add_filter( 'the_content', [ $this, 'filter_content' ], 15 );
// Hook personnalise pour extensibilite
add_action( 'init', [ $this, 'expose_custom_hooks' ] );
}
public function on_save_post( $post_id, $post, $update ) {
if ( wp_is_post_autosave( $post_id ) ) return;
do_action( 'monplugin_after_save_post', $post_id, $post );
}
public function filter_content( $content ) {
if ( ! is_single() ) return $content;
return apply_filters( 'monplugin_content', $content );
}
}
// Bootstrap
Mon_Plugin_Hooks::get_instance()->register();
Hooks conditionnels : cibler précisément l’exécution
WordPress offre de nombreuses fonctions conditionnelles (is_single(), is_admin(), is_user_logged_in()…) qui permettent de cibler l’exécution d’un hook. Mais attention : ces fonctions ne sont pas disponibles à toutes les étapes du cycle de vie WordPress. is_single() retourne toujours false si vous l’appelez dans le hook « plugins_loaded » ou « init » car la requête n’a pas encore été parsée. Utilisez-la à partir du hook « wp » ou « template_redirect » au plus tôt.
Pour les hooks qui se déclenchent dans l’administration (admin_init, admin_enqueue_scripts), vérifiez toujours is_admin() avant d’y enregistrer des callbacks depuis un hook plus précoce. Certains hooks comme « save_post » se déclenchent à la fois en front-end (lors d’une soumission XML-RPC ou API REST) et en back-office, donc ne présumez pas du contexte d’exécution sans le tester.
Pour les sites multisite, ajoutez des vérifications avec is_multisite(), is_main_site() ou is_super_admin() selon le comportement attendu. Un hook enregistré dans mu-plugins s’exécute sur tous les sites du réseau : si votre callback modifie des données, assurez-vous que cette modification est bien intentionnelle pour chaque site. La fonction switch_to_blog() permet d’exécuter du code dans le contexte d’un site spécifique avant de restaurer le contexte courant avec restore_current_blog().
Supprimer et remplacer des hooks existants
remove_action() et remove_filter() permettent de désactiver un callback déjà enregistré. Pour que cela fonctionne, vous devez passer exactement les mêmes paramètres que lors de l’add_action() d’origine : même nom de hook, même callback, même priorité. L’oubli de la priorité est la cause la plus fréquente d’échec : si le callback original est enregistré avec la priorité 20, remove_action(« le_hook », « le_callback ») sans priorité (qui vaut 10 par défaut) ne supprimera rien.
Supprimer un hook d’un plugin ou du core de WordPress est parfois nécessaire mais toujours risqué : le comportement supprimé peut être attendu par d’autres composants. Documentez systématiquement chaque remove_action/filter avec un commentaire expliquant pourquoi vous supprimez ce hook et quelle alternative vous proposez. Si possible, utilisez has_action() ou has_filter() avant de supprimer pour vérifier que le hook est bien enregistré et éviter des erreurs silencieuses.
Pour remplacer complètement un comportement du core, un pattern courant est : 1) supprimer le hook original, 2) enregistrer votre propre implémentation. Par exemple, pour remplacer la fonction d’envoi d’email de réinitialisation de mot de passe, supprimez le callback sur « retrieve_password_key » et enregistrez le vôtre. Ce pattern est préférable à la modification directe des fichiers core car il survit aux mises à jour WordPress et reste compatible avec les plugins qui s’attendent à ce hook.
Créer vos propres hooks : bonnes pratiques
Si vous développez un plugin ou un thème destiné à être étendu, exposez vos points d’extension via des hooks bien documentés. Créez des actions avant et après les opérations importantes (do_action(« myplugin_before_process »), do_action(« myplugin_after_process »)) et des filtres sur toutes les valeurs qui méritent d’être personnalisées. Cette architecture rend votre code extensible sans nécessiter de modifications directes, suivant le principe ouvert/fermé.
Documentez vos hooks avec des docblocks complets : description du hook, liste des arguments passés avec leurs types, moment du cycle de vie WordPress où le hook est déclenché et exemple d’utilisation. Les outils comme WP Parser (utilisé pour générer developer.wordpress.org) peuvent parser ces docblocks et générer automatiquement une documentation en ligne. Respectez les conventions de nommage WordPress : préfixe du plugin + verbe + nom (ex. myplugin_filter_price, myplugin_save_order_complete).
Versionnez vos hooks en notant dans la documentation depuis quelle version ils sont disponibles. Si vous devez supprimer un hook dans une version future, introduisez d’abord une période de dépréciation en déclenchant le hook obsolète avec un message de dépréciation via _doing_it_wrong(). Cette discipline de gestion des hooks est ce qui distingue un plugin professionnel distribué sur WordPress.org d’un plugin amateur difficile à maintenir sur le long terme.
Déboguer les hooks : outils et techniques
Query Monitor est l’outil de débogage WordPress le plus complet pour analyser les hooks. Son onglet « Hooks & Actions » liste tous les hooks déclenchés pendant la requête, avec pour chaque hook la liste des callbacks enregistrés, leurs priorités et leur temps d’exécution. C’est indispensable pour diagnostiquer pourquoi un callback ne s’exécute pas ou pourquoi une valeur filtrée n’est pas celle attendue.
Pour déboguer un filtre spécifique, une technique simple est d’enregistrer un callback temporaire avec une priorité très haute (999) qui log la valeur reçue : add_filter(« the_content », function($c){ error_log(print_r($c,1)); return $c; }, 999). Cela vous montre la valeur du contenu après tous les autres filtres. Pensez à supprimer ce debug code avant de déployer en production.
La fonction wp_hook_build_unique_id() génère l’identifiant interne utilisé par WordPress pour indexer les callbacks. Comprendre ce mécanisme aide à diagnostiquer les échecs de remove_action/filter. Pour les projets complexes, envisagez d’écrire des tests unitaires avec WP_Mock ou Brain Monkey qui permettent de tester vos callbacks de hooks sans bootstrapper WordPress entier, ce qui accélère considérablement le cycle de développement et réduit les régressions.
Commentaires (0)
Laisser un commentaire
Les commentaires sont modérés. Questions WordPress, cybersécurité ou dev web bienvenues.