remove_action o remove_filter con classi esterne?

9 dic 2011, 19:54:35
Visualizzazioni: 69.4K
Voti: 77

In una situazione dove un plugin ha incapsulato i suoi metodi all'interno di una classe e poi ha registrato un filtro o un'azione contro uno di questi metodi, come si rimuove l'azione o il filtro se non si ha più accesso all'istanza di quella classe?

Per esempio, supponiamo di avere un plugin che fa questo:

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

    function my_action() {
       // esegui operazioni...
    }
}

new MyClass();

Notando che ora non ho modo di accedere all'istanza, come faccio a deregistrare la classe? Questo: remove_action( "plugins_loaded", array( MyClass, 'my_action' ) ); non sembra essere l'approccio corretto - almeno, non ha funzionato nel mio caso.

1
Commenti

N/P. Quello qui sotto funziona per te?

kaiser kaiser
9 dic 2011 23:20:33
Tutte le risposte alla domanda 9
5
97

Ogni volta che un plugin crea un new MyClass();, dovrebbe assegnarlo a una variabile con un nome univoco. In questo modo, l'istanza della classe è accessibile.

Quindi, se stava facendo $myclass = new MyClass();, allora potresti fare questo:

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

Questo funziona perché i plugin sono inclusi nello spazio dei nomi globale, quindi le dichiarazioni implicite di variabili nel corpo principale di un plugin sono variabili globali.

Se il plugin non salva l'identificatore della nuova classe da qualche parte, allora tecnicamente è un bug. Uno dei principi generali della programmazione orientata agli oggetti è che gli oggetti che non sono referenziati da qualche variabile da qualche parte sono soggetti a pulizia o eliminazione.

Ora, PHP in particolare non fa questo come farebbe Java, perché PHP è un'implementazione OOP un po' approssimativa. Le variabili di istanza sono semplicemente stringhe con nomi di oggetti univoci, più o meno. Funzionano solo grazie al modo in cui l'interazione del nome della variabile funziona con l'operatore ->. Quindi fare semplicemente new class() può effettivamente funzionare perfettamente, anche se in modo stupido. :)

Quindi, in sintesi, non fare mai new class();. Fai $var = new class(); e rendi quella $var accessibile in qualche modo per consentire ad altre parti di riferirsi ad essa.

Modifica: anni dopo

Una cosa che ho visto fare a molti plugin è utilizzare qualcosa simile al pattern "Singleton". Creano un metodo getInstance() per ottenere l'unica istanza della classe. Questa è probabilmente la soluzione migliore che ho visto. Esempio di plugin:

class ExamplePlugin
{
    protected static $instance = NULL;

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

La prima volta che getInstance() viene chiamato, istanzia la classe e salva il suo puntatore. Puoi usarlo per agganciare azioni.

Un problema con questo è che non puoi usare getInstance() all'interno del costruttore se usi una cosa del genere. Questo perché il new chiama il costruttore prima di impostare $instance, quindi chiamare getInstance() dal costruttore porta a un ciclo infinito e rompe tutto.

Una soluzione alternativa è non usare il costruttore (o almeno non usare getInstance() al suo interno), ma avere esplicitamente una funzione "init" nella classe per impostare le tue azioni e simili. In questo modo:

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

Con qualcosa del genere, alla fine del file, dopo che la classe è stata definita e tutto il resto, istanziare il plugin diventa semplice come questo:

ExamplePlugin::init();

Init inizia ad aggiungere le tue azioni, e così facendo chiama getInstance(), che istanzia la classe e assicura che ne esista solo una. Se non hai una funzione init, faresti questo per istanziare la classe inizialmente:

ExamplePlugin::getInstance();

Per rispondere alla domanda originale, rimuovere quell'hook dell'azione dall'esterno (ovvero, in un altro plugin) può quindi essere fatto così:

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

Metti questo in qualcosa agganciato all'hook dell'azione plugins_loaded e annullerà l'azione agganciata dal plugin originale.

11 dic 2011 01:39:30
Commenti

+1 Verissimo. Questa è chiaramente una best practice. Dovremmo tutti impegnarci a scrivere il codice dei nostri plugin in questo modo.

Tom Auger Tom Auger
24 apr 2012 20:41:18

+1 queste istruzioni mi hanno davvero aiutato a rimuovere un filtro in una classe con pattern singleton.

Devin Walker Devin Walker
28 mar 2014 04:01:01

+1, ma penso che in generale dovresti agganciarti a wp_loaded, non a plugins_loaded, che potrebbe essere chiamato troppo presto.

EML EML
28 apr 2015 16:00:00

No, plugins_loaded sarebbe il posto corretto. L'azione wp_loaded avviene dopo l'azione init, quindi se il tuo plugin esegue azioni su init (e la maggior parte lo fa), allora vuoi inizializzare il plugin e configurarlo prima di quello. L'hook plugins_loaded è il posto giusto per quella fase di costruzione.

Otto Otto
28 apr 2015 16:26:11

Questa risposta potrebbe arrivare un po' tardi. Ma grazie per questo contributo. Dopo aver cercato per ore abbiamo scoperto che non abbiamo rispettato i principi generali della Programmazione Orientata agli Oggetti che afferma che gli oggetti che non sono referenziati da qualche variabile da qualche parte sono soggetti a pulizia o eliminazione.

NME New Media Entertainment NME New Media Entertainment
21 gen 2020 03:45:20
3
24

La cosa migliore da fare qui è utilizzare una classe statica. Il seguente codice dovrebbe essere istruttivo:

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' );

Se esegui questo codice da un plugin, dovresti notare che il metodo della StaticClass così come la funzione verranno rimossi da wp_footer.

10 dic 2011 23:25:25
Commenti

Punto preso, ma non tutte le classi possono essere semplicemente convertite in statiche.

Geert Geert
29 feb 2012 13:40:37

Ho accettato questa risposta perché risponde alla domanda in modo più diretto, anche se la risposta di Otto è la best practice. Noto qui che non penso sia necessario dichiarare esplicitamente static. È stata mia esperienza (anche se potrei sbagliarmi) che puoi trattare la funzione come se fosse statica array( 'MyClass', 'member_function' ) e spesso funziona senza la keyword 'static'.

Tom Auger Tom Auger
24 apr 2012 20:40:52

@TomAuger no non puoi, SOLO se è aggiunta come classe statica puoi usare la funzione remove_action, altrimenti non funzionerà...ecco perché ho dovuto scrivere la mia funzione per gestire quando non è una classe statica. Questa risposta sarebbe la migliore solo se la tua domanda riguardasse il tuo codice, altrimenti proveresti a rimuovere un altro filtro/azione dal codice di qualcun altro e non puoi cambiarlo in statico

sMyles sMyles
20 set 2016 03:23:38
3
24

2 piccole funzioni PHP per permettere la rimozione di filtri/azioni con classi "anonime": https://github.com/herewithme/wp-filters-extras/

6 nov 2012 09:55:57
Commenti

Funzioni davvero interessanti. Grazie per averle condivise qui!

Tom Auger Tom Auger
6 nov 2012 16:22:01

Come menzionato da altri nel mio post qui sotto, queste funzioni non funzioneranno in WordPress 4.7 (a meno che il repository non venga aggiornato, ma non lo è da 2 anni)

sMyles sMyles
20 set 2016 03:25:39

Vorrei solo far notare che il repository wp-filters-extras è stato effettivamente aggiornato per la versione 4.7 e la classe WP_Hook.

Dave Romsey Dave Romsey
4 mag 2017 09:28:25
6
17

Ecco una funzione estesamente documentata che ho creato per rimuovere i filtri quando non si ha accesso all'oggetto della classe (funziona con WordPress 1.2+, incluso 4.7+):

https://gist.github.com/tripflex/c6518efc1753cf2392559866b4bd1a53

/**
 * Rimuovi Filtro di Classe Senza Accesso all'Oggetto della Classe
 *
 * Per utilizzare la funzione core di WordPress remove_filter() su un filtro aggiunto con il callback
 * a una classe, è necessario avere accesso all'oggetto della classe oppure deve essere una chiamata
 * a un metodo statico. Questa funzione permette di rimuovere filtri con un callback a una classe
 * a cui non si ha accesso.
 *
 * Funziona con WordPress 1.2+ (supporto per 4.7+ aggiunto il 19-09-2016)
 * Aggiornato il 27-02-2017 per utilizzare la rimozione interna di WordPress per 4.7+ (per prevenire avvisi PHP in output)
 *
 * @param string $tag         Filtro da rimuovere
 * @param string $class_name  Nome della classe per il callback del filtro
 * @param string $method_name Nome del metodo per il callback del filtro
 * @param int    $priority    Priorità del filtro (default 10)
 *
 * @return bool Indica se la funzione è stata rimossa.
 */
function remove_class_filter( $tag, $class_name = '', $method_name = '', $priority = 10 ) {
    global $wp_filter;

    // Verifica prima che il filtro esista
    if ( ! isset( $wp_filter[ $tag ] ) ) return FALSE;

    /**
     * Se la configurazione del filtro è un oggetto, significa che stiamo usando WordPress 4.7+ e la configurazione non è più
     * un semplice array, ma un oggetto che implementa l'interfaccia ArrayAccess.
     *
     * Per mantenere la compatibilità con le versioni precedenti, impostiamo $callbacks uguale all'array corretto come riferimento (così $wp_filter viene aggiornato)
     *
     * @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 ) ) {
        // Crea l'oggetto $fob dal tag del filtro, da usare sotto
        $fob = $wp_filter[ $tag ];
        $callbacks = &$wp_filter[ $tag ]->callbacks;
    } else {
        $callbacks = &$wp_filter[ $tag ];
    }

    // Esci se non ci sono callback per la priorità specificata
    if ( ! isset( $callbacks[ $priority ] ) || empty( $callbacks[ $priority ] ) ) return FALSE;

    // Scorri ogni filtro per la priorità specificata, cercando la nostra classe & metodo
    foreach( (array) $callbacks[ $priority ] as $filter_id => $filter ) {

        // Il filtro dovrebbe sempre essere un array - array( $this, 'method' ), se no passa al prossimo
        if ( ! isset( $filter[ 'function' ] ) || ! is_array( $filter[ 'function' ] ) ) continue;

        // Se il primo valore nell'array non è un oggetto, non può essere una classe
        if ( ! is_object( $filter[ 'function' ][ 0 ] ) continue;

        // Il metodo non corrisponde a quello che stiamo cercando, passa al prossimo
        if ( $filter[ 'function' ][ 1 ] !== $method_name ) continue;

        // Il metodo corrisponde, ora controlliamo la Classe
        if ( get_class( $filter[ 'function' ][ 0 ] ) === $class_name ) {

            // WordPress 4.7+ utilizza la funzione core remove_filter() poiché abbiamo trovato l'oggetto della classe
            if( isset( $fob ) ){
                // Gestisce la rimozione del filtro, ripristinando le chiavi di priorità dei callback durante l'iterazione, ecc.
                $fob->remove_filter( $tag, $filter['function'], $priority );

            } else {
                // Usa il processo di rimozione legacy (pre 4.7)
                unset( $callbacks[ $priority ][ $filter_id ] );
                // e se era l'unico filtro in quella priorità, rimuovi quella priorità
                if ( empty( $callbacks[ $priority ] ) ) {
                    unset( $callbacks[ $priority ] );
                }
                // e se l'unico filtro per quel tag, imposta il tag come array vuoto
                if ( empty( $callbacks ) ) {
                    $callbacks = array();
                }
                // Rimuovi questo filtro da merged_filters, che specifica se i filtri sono stati ordinati
                unset( $GLOBALS['merged_filters'][ $tag ] );
            }

            return TRUE;
        }
    }

    return FALSE;
}

/**
 * Rimuovi Azione di Classe Senza Accesso all'Oggetto della Classe
 *
 * Per utilizzare la funzione core di WordPress remove_action() su un'azione aggiunta con il callback
 * a una classe, è necessario avere accesso all'oggetto della classe oppure deve essere una chiamata
 * a un metodo statico. Questa funzione permette di rimuovere azioni con un callback a una classe
 * a cui non si ha accesso.
 *
 * Funziona con WordPress 1.2+ (supporto per 4.7+ aggiunto il 19-09-2016)
 *
 * @param string $tag         Azione da rimuovere
 * @param string $class_name  Nome della classe per il callback dell'azione
 * @param string $method_name Nome del metodo per il callback dell'azione
 * @param int    $priority    Priorità dell'azione (default 10)
 *
 * @return bool               Indica se la funzione è stata rimossa.
 */
function remove_class_action( $tag, $class_name = '', $method_name = '', $priority = 10 ) {
    return remove_class_filter( $tag, $class_name, $method_name, $priority );
}
15 set 2016 22:58:52
Commenti

Domanda - l'hai testato in 4.7? Ci sono stati alcuni cambiamenti nel modo in cui le callback vengono registrate nei filtri che sono completamente nuovi. Non ho analizzato il tuo codice in profondità, ma è qualcosa che potresti voler verificare: https://make.wordpress.org/core/2016/09/08/wp_hook-next-generation-actions-and-filters/

Tom Auger Tom Auger
15 set 2016 23:59:45

sì, sono abbastanza sicuro che questo non funzionerà in 4.7

gmazzap gmazzap
16 set 2016 01:01:53

Ahh! No non l'ho fatto ma grazie, sicuramente ci darò un'occhiata e aggiornerò questo codice per renderlo compatibile (se necessario)

sMyles sMyles
17 set 2016 22:54:19

@TomAuger grazie per la segnalazione! Ho aggiornato la funzione, testata e funzionante su WordPress 4.7+ (mantenendo comunque la retrocompatibilità)

sMyles sMyles
20 set 2016 03:10:29

@gmazzap ho aggiornato anche il Gist e aggiunto remove_class_action

sMyles sMyles
20 set 2016 03:11:02

Appena aggiornato per utilizzare il metodo di rimozione interno del core (per gestire la rimozione durante l'iterazione ed evitare warning php)

sMyles sMyles
28 feb 2017 01:07:07
Mostra i restanti 1 commenti
2

Le soluzioni precedenti sembrano obsolete, ho dovuto scrivere la mia...

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]) ;
                            }
                        }
                    }
                }
            }
        }
    }
}
25 dic 2017 11:53:20
Commenti

Se stai cercando una soluzione moderna, questa ha funzionato per me con WP 5.6

Alex Alex
16 dic 2020 22:57:18

Confermato, funziona in 5.6.2. Altre soluzioni non funzionano in 5.6

Bret Weinraub Bret Weinraub
10 mar 2021 02:08:57
0

In casi come questo WordPress aggiunge un hash (ID univoco) al nome della funzione e lo memorizza nella variabile globale $wp_filter. Quindi, se usi la funzione remove_filter non accadrà nulla. Anche se aggiungi il nome della classe al nome della funzione come remove_filter('plugins_loaded', ['MyClass', 'my_action']).
Tutto ciò che puoi fare è rimuovere manualmente tutti gli hook my_action dalla variabile globale $wp_filter.

Ecco la funzione per farlo:

function my_remove_filter($tag, $function_name, $priority = 10){

    global $wp_filter;

    if( isset($wp_filter[$tag]->callbacks[$priority]) and !empty($wp_filter[$tag]->callbacks[$priority]) ){

        $wp_filter[$tag]->callbacks[$priority] = array_filter($wp_filter[$tag]->callbacks[$priority], function($v, $k) use ($function_name){

            return ( stripos($k, $function_name) === false );

        }, ARRAY_FILTER_USE_BOTH );
    }
}

Usala così:

my_remove_filter('plugins_loaded', 'my_action');
13 nov 2019 21:49:15
0

Questa non è una risposta generica, ma specifica per il tema Avada e WooCommerce, che penso possa essere utile ad altre persone:

function remove_woo_commerce_hooks() {
    global $avada_woocommerce;
    remove_action( 'woocommerce_single_product_summary', array( $avada_woocommerce, 'add_product_border' ), 19 );
}
add_action( 'after_setup_theme', 'remove_woo_commerce_hooks' );
6 ago 2019 19:50:15
1

Ho trovato questa Gist che faceva esattamente quello che volevo:

function remove_class_hook( $tag, $class_name = '', $method_name = '', $priority = 10 ) {
    global $wp_filter;
    $is_hook_removed = false;
    if ( ! empty( $wp_filter[ $tag ]->callbacks[ $priority ] ) ) {
        $methods = array_filter(wp_list_pluck(
            $wp_filter[ $tag ]->callbacks[ $priority ],
            'function'
        ), function ($method) {
            /**
             * Consenti solo la notazione array e stringa per gli hook, visto che
             * stiamo cercando di rimuovere un metodo specifico di una classe comunque. E il
             * metodo della classe viene passato come stringa in ogni caso.
             */
            return is_string($method) || is_array($method);
        });
        $found_hooks = ! empty( $methods ) ? wp_list_filter( $methods, array( 1 => $method_name ) ) : array();
        foreach( $found_hooks as $hook_key => $hook ) {
            if ( ! empty( $hook[0] ) && is_object( $hook[0] ) && get_class( $hook[0] ) === $class_name ) {
                $wp_filter[ $tag ]->remove_filter( $tag, $hook, $priority );
                $is_hook_removed = true;
            }
        }
    }
    return $is_hook_removed;
}
15 gen 2024 20:37:11
Commenti

Credo che TripFlex (l'autore del gist che hai trovato) sia effettivamente @sMyles, la cui risposta (https://wordpress.stackexchange.com/a/239431/3687) è ora la risposta accettata.

Tom Auger Tom Auger
18 gen 2024 00:02:43
0

Questa funzione si basa sulla risposta di @Digerkam. Ho aggiunto un controllo per verificare se $def['function'][0] è una stringa e alla fine ha funzionato per me.

Inoltre, utilizzare $wp_filter[$tag]->remove_filter() dovrebbe renderla più stabile.

function remove_class_action($tag, $class = '', $method, $priority = null) : bool {
    global $wp_filter;
    if (isset($wp_filter[$tag])) {
        $len = strlen($method);

        foreach($wp_filter[$tag] as $_priority => $actions) {

            if ($actions) {
                foreach($actions as $function_key => $data) {

                    if ($data) {
                        if (substr($function_key, -$len) == $method) {

                            if ($class !== '') {
                                $_class = '';
                                if (is_string($data['function'][0])) {
                                    $_class = $data['function'][0];
                                }
                                elseif (is_object($data['function'][0])) {
                                    $_class = get_class($data['function'][0]);
                                }
                                else {
                                    return false;
                                }

                                if ($_class !== '' && $_class == $class) {
                                    if (is_numeric($priority)) {
                                        if ($_priority == $priority) {
                                            //if (isset( $wp_filter->callbacks[$_priority][$function_key])) {}
                                            return $wp_filter[$tag]->remove_filter($tag, $function_key, $_priority);
                                        }
                                    }
                                    else {
                                        return $wp_filter[$tag]->remove_filter($tag, $function_key, $_priority);
                                    }
                                }
                            }
                            else {
                                if (is_numeric($priority)) {
                                    if ($_priority == $priority) {
                                        return $wp_filter[$tag]->remove_filter($tag, $function_key, $_priority);
                                    }
                                }
                                else {
                                    return $wp_filter[$tag]->remove_filter($tag, $function_key, $_priority);
                                }
                            }

                        }
                    }
                }
            }
        }

    }

    return false;
}

Esempio di utilizzo:

Corrispondenza esatta

add_action('plugins_loaded', function() {
    remove_class_action('plugins_loaded', 'MyClass', 'my_action', 0);
});

Qualsiasi priorità

add_action('plugins_loaded', function() {
    remove_class_action('plugins_loaded', 'MyClass', 'my_action');
});

Qualsiasi classe e qualsiasi priorità

add_action('plugins_loaded', function() {
    remove_class_action('plugins_loaded', '', 'my_action');
});
29 mag 2019 09:50:16