remove_action ou remove_filter avec des classes externes?

55

Dans les cas où un plugin a encapsulé ses méthodes dans une classe, puis enregistré un filtre ou une action contre l'une de ces méthodes, comment supprimer l'action ou le filtre si vous n'avez plus accès à l'instance de cette classe?

Par exemple, supposons que vous ayez un plugin qui effectue ceci:

class MyClass {
    function __construct() {
       add_action( "plugins_loaded", array( $this, 'my_action' ) );
    }

    function my_action() {
       // do stuff...
    }
}

new MyClass();

Notant que je n'ai maintenant aucun moyen d'accéder à l'instance, comment puis-je annuler l'enregistrement de la classe? Ceci: remove_action( "plugins_loaded", array( MyClass, 'my_action' ) ); ne semble pas être la bonne approche - du moins, cela ne semblait pas fonctionner dans mon cas.

    
posée Tom Auger 09.12.2011 - 18:54

5 réponses

15

La meilleure chose à faire ici est d’utiliser une classe statique. Le code suivant devrait être instructif:

class MyClass {
    function __construct() {
        add_action( 'wp_footer', array( $this, 'my_action' ) );
    }
    function my_action() {
        print '<h1>' . __class__ . ' - ' . __function__ . '</h1>';
    }
}
new MyClass();


class MyStaticClass {
    public static function init() {
        add_action( 'wp_footer', array( __class__, 'my_action' ) );
    }
    public static function my_action() {
        print '<h1>' . __class__ . ' - ' . __function__ . '</h1>';
    }
}
MyStaticClass::init();

function my_wp_footer() {
    print '<h1>my_wp_footer()</h1>';
}
add_action( 'wp_footer', 'my_wp_footer' );

function mfields_test_remove_actions() {
    remove_action( 'wp_footer', 'my_wp_footer' );
    remove_action( 'wp_footer', array( 'MyClass', 'my_action' ), 10 );
    remove_action( 'wp_footer', array( 'MyStaticClass', 'my_action' ), 10 );
}
add_action( 'wp_head', 'mfields_test_remove_actions' );

Si vous exécutez ce code depuis un plugin, vous remarquerez que la méthode de StaticClass ainsi que la fonction seront supprimées de wp_footer.

    
réponse donnée mfields 10.12.2011 - 22:25
75

Lorsqu'un plugin crée un new MyClass(); , il doit l'affecter à une variable portant un nom unique. Ainsi, l’instance de la classe est accessible.

Donc s'il faisait $myclass = new MyClass(); , vous pourriez faire ceci:

global $myclass;
remove_action( 'wp_footer', array( $myclass, 'my_action' ) );

Cela fonctionne car les plugins sont inclus dans l'espace de noms global. Par conséquent, les déclarations de variables implicites dans le corps principal d'un plugin sont des variables globales.

Si le plug-in n'enregistre pas l'identifiant de la nouvelle classe quelque part , techniquement, il s'agit d'un bogue. L'un des principes généraux de la programmation orientée objet est que les objets qui ne sont pas référencés par une variable quelque part sont sujets à nettoyage ou à élimination.

Désormais, PHP, en particulier, ne le fait pas comme le ferait Java, car PHP est en quelque sorte une implémentation POO semi-arse. Les variables d'instance sont juste des chaînes avec des noms d'objet uniques, en quelque sorte. Ils ne fonctionnent que parce que l'interaction du nom de la fonction variable fonctionne avec l'opérateur -> . Donc, le simple fait de new class() peut fonctionner parfaitement, simplement bêtement. :)

Donc, au bout du compte, ne faites jamais new class(); . Faites $var = new class(); et rendez cette variable $ var accessible aux autres bits pour la référencer.

Modifier: des années plus tard

Une chose que j’ai vu beaucoup de plugins faire est d’utiliser quelque chose de similaire au motif "Singleton". Ils créent une méthode getInstance () pour obtenir l'instance unique de la classe. C'est probablement la meilleure solution que j'ai vue. Exemple de plugin:

class ExamplePlugin
{
    protected static $instance = NULL;

    public static function getInstance() {
        NULL === self::$instance and self::$instance = new self;
        return self::$instance;
    }
}

La première fois que getInstance () est appelé, il instancie la classe et enregistre son pointeur. Vous pouvez l'utiliser pour accrocher des actions.

Un problème avec cela est que vous ne pouvez pas utiliser getInstance () dans le constructeur si vous utilisez une telle chose. En effet, new appelle le constructeur avant de définir l'instance $. L'appel de getInstance () à partir du constructeur conduit donc à une boucle infinie et interrompt tout.

Une solution de contournement consiste à ne pas utiliser le constructeur (ou, du moins, à ne pas utiliser getInstance () dans celui-ci), mais à avoir explicitement une fonction "init" dans la classe pour configurer vos actions, etc. Comme ceci:

public static function init() {
    add_action( 'wp_footer', array( ExamplePlugin::getInstance(), 'my_action' ) );
}

Avec quelque chose comme ceci, à la fin du fichier, après que la classe ait été définie et telle, instancier le plugin devient aussi simple que cela:

ExamplePlugin::init();

Init commence à ajouter vos actions et appelle de la sorte getInstance (), qui instancie la classe et s'assure que seul l'un d'entre eux existe. Si vous n'avez pas de fonction init, faites ceci pour instancier initialement la classe:

ExamplePlugin::getInstance();

Pour répondre à la question initiale, supprimer ce crochet d'action de l'extérieur (c'est-à-dire dans un autre plugin) peut alors être effectué comme suit:

remove_action( 'wp_footer', array( ExamplePlugin::getInstance(), 'my_action' ) );

Mettez cela dans quelque chose qui est accroché au hook d'action plugins_loaded et cela annulera l'action qui est accrochée par le plugin d'origine.

    
réponse donnée Otto 11.12.2011 - 00:39
12

2 petites fonctions PHP permettant de supprimer les filtres / actions avec la classe "anonymous": enlace

    
réponse donnée herewithme 06.11.2012 - 08:55
12

Voici une fonction très documentée que j'ai créée pour supprimer les filtres lorsque vous n'avez pas accès à l'objet de classe (fonctionne avec WordPress 1.2+, y compris 4.7 +):

enlace

/**
 * Remove Class Filter Without Access to Class Object
 *
 * In order to use the core WordPress remove_filter() on a filter added with the callback
 * to a class, you either have to have access to that class object, or it has to be a call
 * to a static method.  This method allows you to remove filters with a callback to a class
 * you don't have access to.
 *
 * Works with WordPress 1.2+ (4.7+ support added 9-19-2016)
 * Updated 2-27-2017 to use internal WordPress removal for 4.7+ (to prevent PHP warnings output)
 *
 * @param string $tag         Filter to remove
 * @param string $class_name  Class name for the filter's callback
 * @param string $method_name Method name for the filter's callback
 * @param int    $priority    Priority of the filter (default 10)
 *
 * @return bool Whether the function is removed.
 */
function remove_class_filter( $tag, $class_name = '', $method_name = '', $priority = 10 ) {
    global $wp_filter;

    // Check that filter actually exists first
    if ( ! isset( $wp_filter[ $tag ] ) ) return FALSE;

    /**
     * If filter config is an object, means we're using WordPress 4.7+ and the config is no longer
     * a simple array, rather it is an object that implements the ArrayAccess interface.
     *
     * To be backwards compatible, we set $callbacks equal to the correct array as a reference (so $wp_filter is updated)
     *
     * @see https://make.wordpress.org/core/2016/09/08/wp_hook-next-generation-actions-and-filters/
     */
    if ( is_object( $wp_filter[ $tag ] ) && isset( $wp_filter[ $tag ]->callbacks ) ) {
        // Create $fob object from filter tag, to use below
        $fob = $wp_filter[ $tag ];
        $callbacks = &$wp_filter[ $tag ]->callbacks;
    } else {
        $callbacks = &$wp_filter[ $tag ];
    }

    // Exit if there aren't any callbacks for specified priority
    if ( ! isset( $callbacks[ $priority ] ) || empty( $callbacks[ $priority ] ) ) return FALSE;

    // Loop through each filter for the specified priority, looking for our class & method
    foreach( (array) $callbacks[ $priority ] as $filter_id => $filter ) {

        // Filter should always be an array - array( $this, 'method' ), if not goto next
        if ( ! isset( $filter[ 'function' ] ) || ! is_array( $filter[ 'function' ] ) ) continue;

        // If first value in array is not an object, it can't be a class
        if ( ! is_object( $filter[ 'function' ][ 0 ] ) ) continue;

        // Method doesn't match the one we're looking for, goto next
        if ( $filter[ 'function' ][ 1 ] !== $method_name ) continue;

        // Method matched, now let's check the Class
        if ( get_class( $filter[ 'function' ][ 0 ] ) === $class_name ) {

            // WordPress 4.7+ use core remove_filter() since we found the class object
            if( isset( $fob ) ){
                // Handles removing filter, reseting callback priority keys mid-iteration, etc.
                $fob->remove_filter( $tag, $filter['function'], $priority );

            } else {
                // Use legacy removal process (pre 4.7)
                unset( $callbacks[ $priority ][ $filter_id ] );
                // and if it was the only filter in that priority, unset that priority
                if ( empty( $callbacks[ $priority ] ) ) {
                    unset( $callbacks[ $priority ] );
                }
                // and if the only filter for that tag, set the tag to an empty array
                if ( empty( $callbacks ) ) {
                    $callbacks = array();
                }
                // Remove this filter from merged_filters, which specifies if filters have been sorted
                unset( $GLOBALS['merged_filters'][ $tag ] );
            }

            return TRUE;
        }
    }

    return FALSE;
}

/**
 * Remove Class Action Without Access to Class Object
 *
 * In order to use the core WordPress remove_action() on an action added with the callback
 * to a class, you either have to have access to that class object, or it has to be a call
 * to a static method.  This method allows you to remove actions with a callback to a class
 * you don't have access to.
 *
 * Works with WordPress 1.2+ (4.7+ support added 9-19-2016)
 *
 * @param string $tag         Action to remove
 * @param string $class_name  Class name for the action's callback
 * @param string $method_name Method name for the action's callback
 * @param int    $priority    Priority of the action (default 10)
 *
 * @return bool               Whether the function is removed.
 */
function remove_class_action( $tag, $class_name = '', $method_name = '', $priority = 10 ) {
    remove_class_filter( $tag, $class_name, $method_name, $priority );
}
    
réponse donnée sMyles 15.09.2016 - 21:58
2

Les solutions ci-dessus semblent obsolètes, j'ai dû écrire la mienne ...

function remove_class_action ($action,$class,$method) {
    global $wp_filter ;
    if (isset($wp_filter[$action])) {
        $len = strlen($method) ;
        foreach ($wp_filter[$action] as $pri => $actions) {
            foreach ($actions as $name => $def) {
                if (substr($name,-$len) == $method) {
                    if (is_array($def['function'])) {
                        if (get_class($def['function'][0]) == $class) {
                            if (is_object($wp_filter[$action]) && isset($wp_filter[$action]->callbacks)) {
                                unset($wp_filter[$action]->callbacks[$pri][$name]) ;
                            } else {
                                unset($wp_filter[$action][$pri][$name]) ;
                            }
                        }
                    }
                }
            }
        }
    }
}
    
réponse donnée Digerkam 25.12.2017 - 10:53

Lire d'autres questions sur les étiquettes