# remove_action sur une méthode de classe anonyme : débrancher un hook tenace

> Un hook accroché par une instance à laquelle vous n'avez pas accès résiste à remove_action. Voici comment le retrouver dans $wp_filter et le neutraliser proprement.

- Auteur : Clément Hadrot
- Publié le : 2023-01-02
- Mis à jour le : 2023-01-02
- Catégorie : Extensions
- URL : https://wpmoderne.dev.wordpress-developpement.fr/extensions/remove-action-methode-classe-anonyme-hook-tenace/

## L’essentiel

- remove_action exige la même instance ou closure
- Parcourir $wp_filter pour identifier le callback
- Préférer des priorités plus tardives à la chirurgie

Symptôme : un client signale qu'un message d'avertissement s'affiche en haut de toutes les pages de son site, ajouté par une extension tierce qu'il ne veut plus voir. Le code source de cette extension montre un `add_action( 'admin_notices', array( $this, 'afficher_avertissement' ) )` exécuté depuis une méthode d'initialisation, sur une instance créée à la volée et jamais stockée dans une variable globale accessible. Un `remove_action( 'admin_notices', array( $obj, 'afficher_avertissement' ) )` classique ne fonctionne évidemment pas : on n'a pas d'accès à `$obj`.

C'est un cas fréquent avec les extensions qui instancient leur classe principale directement à l'intérieur d'un fichier, sans exposer de point d'accès global ni de fonction singleton. Le hook est bien là, actif, mais invisible depuis l'extérieur au sens strict de l'API PHP.

## Pourquoi remove_action échoue silencieusement

WordPress identifie un callback enregistré avec une méthode d'objet par un hash calculé à partir du *spl_object_hash* de l'instance (ou, pour PHP récent, de son identifiant d'objet) combiné au nom de la méthode. `remove_action()` ne supprime un callback que si vous lui fournissez exactement la même paire objet/méthode, ou exactement la même closure. Sans référence à l'instance d'origine, impossible de reconstituer ce hash à la main : la fonction renvoie `false`, sans avertissement, et le hook reste actif.

Le même problème se pose avec les fonctions anonymes : une `closure` déclarée inline dans un `add_action` ne peut être retirée par un `remove_action` ultérieur que si vous conservez une référence à cette closure précise — recréer une closure identique en apparence ne suffit pas, PHP les considère comme deux objets distincts.

## Retrouver le callback dans $wp_filter

La structure globale `$wp_filter` contient, pour chaque hook, un objet `WP_Hook` qui expose la liste des callbacks enregistrés, classés par priorité. On peut l'inspecter pour comprendre ce qui est réellement accroché :

> L'essentiel à retenir : remove_action exige la même instance ou closure ; Parcourir $wp_filter pour identifier le callback ; Préférer des priorités plus tardives à la chirurgie

```
add_action( 'admin_notices', function() {
    global $wp_filter;
    if ( empty( $wp_filter['admin_notices'] ) ) {
        return;
    }
    foreach ( $wp_filter['admin_notices']->callbacks as $priorite => $callbacks ) {
        foreach ( $callbacks as $id => $donnees ) {
            $cible = $donnees['function'];
            if ( is_array( $cible ) && is_object( $cible[0] ) ) {
                error_log( sprintf(
                    'Hook admin_notices, priorité %s : classe %s, méthode %s',
                    $priorite,
                    get_class( $cible[0] ),
                    $cible[1]
                ) );
            }
        }
    }
}, 1 );
```

Ce journal permet d'identifier précisément la classe fautive et sa méthode. Une fois la classe connue, plusieurs stratégies s'offrent à vous, du plus propre au plus radical.

## Solution 1 : retirer directement l'entrée de $wp_filter

Puisque l'objet `WP_Hook` est accessible globalement, on peut manipuler sa structure interne pour retirer un callback identifié par sa classe, sans avoir besoin de l'instance :

```
add_action( 'wp_loaded', function() {
    global $wp_filter;
    if ( empty( $wp_filter['admin_notices'] ) ) {
        return;
    }
    foreach ( $wp_filter['admin_notices']->callbacks as $priorite => $callbacks ) {
        foreach ( $callbacks as $id => $donnees ) {
            $cible = $donnees['function'];
            if ( is_array( $cible ) && is_object( $cible[0] )
                && get_class( $cible[0] ) === 'Extension_Tierce_Notices'
                && $cible[1] === 'afficher_avertissement' ) {
                unset( $wp_filter['admin_notices']->callbacks[ $priorite ][ $id ] );
            }
        }
    }
}, 999 );
```

Cette approche fonctionne, mais elle est fragile : elle dépend de la structure interne de `WP_Hook`, qui reste un détail d'implémentation. Elle doit rester une solution de dernier recours, documentée clairement dans le code pour qu'un futur mainteneur comprenne pourquoi elle existe.

## Solution 2 : neutraliser en aval plutôt qu'en amont

Une alternative moins invasive consiste à laisser le hook s'exécuter, mais à masquer son effet après coup, par exemple en interceptant la sortie avec `ob_start()` et un filtre sur le buffer, ou en ciblant le rendu final via CSS si le contenu est prévisible. Pour un message d'admin, on peut aussi jouer sur l'ordre d'exécution : accrocher sa propre fonction sur `admin_notices` avec une priorité très basse, avant le hook fautif, et modifier une option globale que la méthode fautive consulte en interne — cette option n'existant en pratique que rarement.

### Solution 3 : contacter l'auteur ou forker localement

Quand le hook gêne réellement le fonctionnement du site (pas juste un affichage cosmétique), la solution la plus saine reste de signaler le problème à l'auteur de l'extension, ou d'ajouter un filtre de compatibilité si l'extension en propose un. Beaucoup d'extensions bien conçues exposent justement un filtre pour désactiver leurs notices, une piste à vérifier avant de se lancer dans la chirurgie sur `$wp_filter`.

- Chercher dans le code source un filtre du type `nom_extension_disable_notices`.
- Vérifier la documentation ou le support de l'extension avant toute intervention manuelle.
- Documenter systématiquement toute manipulation de `$wp_filter` avec un commentaire expliquant le contexte et la date.

## Prévention côté auteur d'extension

Si vous développez vos propres extensions, évitez ce piège chez vos utilisateurs en exposant toujours un point d'accès à l'instance : une fonction globale `mon_extension()` qui retourne le singleton, ou une constante contenant l'instance. Cela permet à n'importe qui de faire un `remove_action( 'hook', array( mon_extension(), 'methode' ) )` propre, sans avoir à fouiller dans les structures internes de WordPress.

## Ce qu'il faut retenir

Un hook accroché depuis une instance inaccessible n'est pas irréversible, mais il exige de sortir de l'API publique pour aller lire directement `$wp_filter`. Identifiez d'abord précisément la classe et la méthode en cause, cherchez ensuite un filtre de compatibilité officiel, et ne touchez à la structure interne des hooks qu'en dernier recours, avec un commentaire clair sur le pourquoi. C'est un correctif de survie, pas une pratique à généraliser dans du code neuf.
