Come rimuovere un filtro che è un oggetto anonimo?


62

Nel mio functions.phpfile vorrei rimuovere il filtro di seguito, ma non sono sicuro di come farlo poiché è in una classe. Come dovrebbe remove_filter()apparire?

add_filter('comments_array',array( &$this, 'FbComments' ));

È sulla linea 88 qui .


Dovresti rimuoverlo &dal tuo &$this, è una cosa di PHP 4
Tom J Nowell

Risposte:


79

Questa è un'ottima domanda. Passa al cuore oscuro dell'API del plug-in e alle migliori pratiche di programmazione.

Per la seguente risposta ho creato un semplice plugin per illustrare il problema con un codice di facile lettura.

<?php # -*- coding: utf-8 -*-
/* Plugin Name: Anonymous OOP Action */

if ( ! class_exists( 'Anonymous_Object' ) )
{
    /**
     * Add some actions with randomized global identifiers.
     */
    class Anonymous_Object
    {
        public function __construct()
        {
            add_action( 'wp_footer', array ( $this, 'print_message_1' ), 5 );
            add_action( 'wp_footer', array ( $this, 'print_message_2' ), 5 );
            add_action( 'wp_footer', array ( $this, 'print_message_3' ), 12 );
        }

        public function print_message_1()
        {
            print '<p>Kill me!</p>';
        }

        public function print_message_2()
        {
            print '<p>Me too!</p>';
        }

        public function print_message_3()
        {
            print '<p>Aaaand me!</p>';
        }
    }

    // Good luck finding me!
    new Anonymous_Object;
}

Ora vediamo questo:

inserisci qui la descrizione dell'immagine

WordPress ha bisogno di un nome per il filtro. Non ne abbiamo fornito uno, quindi WordPress chiama _wp_filter_build_unique_id()e ne crea uno. Questo nome non è prevedibile perché utilizza spl_object_hash().

Se eseguiamo un var_export()on $GLOBALS['wp_filter'][ 'wp_footer' ]otteniamo qualcosa del genere ora:

array (
  5 => 
  array (
    '000000002296220e0000000013735e2bprint_message_1' => 
    array (
      'function' => 
      array (
        0 => 
        Anonymous_Object::__set_state(array(
        )),
        1 => 'print_message_1',
      ),
      'accepted_args' => 1,
    ),
    '000000002296220e0000000013735e2bprint_message_2' => 
    array (
      'function' => 
      array (
        0 => 
        Anonymous_Object::__set_state(array(
        )),
        1 => 'print_message_2',
      ),
      'accepted_args' => 1,
    ),
  ),
  12 => 
  array (
    '000000002296220e0000000013735e2bprint_message_3' => 
    array (
      'function' => 
      array (
        0 => 
        Anonymous_Object::__set_state(array(
        )),
        1 => 'print_message_3',
      ),
      'accepted_args' => 1,
    ),
  ),
  20 => 
  array (
    'wp_print_footer_scripts' => 
    array (
      'function' => 'wp_print_footer_scripts',
      'accepted_args' => 1,
    ),
  ),
  1000 => 
  array (
    'wp_admin_bar_render' => 
    array (
      'function' => 'wp_admin_bar_render',
      'accepted_args' => 1,
    ),
  ),
)

Per trovare e rimuovere la nostra azione malvagia dobbiamo passare attraverso i filtri associati per l'hook (un'azione è solo un filtro molto semplice), verificare se si tratta di un array e se l'oggetto è un'istanza della classe. Quindi prendiamo la priorità e rimuoviamo il filtro, senza mai vedere l'identificatore reale .

Va bene, mettiamolo in una funzione:

if ( ! function_exists( 'remove_anonymous_object_filter' ) )
{
    /**
     * Remove an anonymous object filter.
     *
     * @param  string $tag    Hook name.
     * @param  string $class  Class name
     * @param  string $method Method name
     * @return void
     */
    function remove_anonymous_object_filter( $tag, $class, $method )
    {
        $filters = $GLOBALS['wp_filter'][ $tag ];

        if ( empty ( $filters ) )
        {
            return;
        }

        foreach ( $filters as $priority => $filter )
        {
            foreach ( $filter as $identifier => $function )
            {
                if ( is_array( $function)
                    and is_a( $function['function'][0], $class )
                    and $method === $function['function'][1]
                )
                {
                    remove_filter(
                        $tag,
                        array ( $function['function'][0], $method ),
                        $priority
                    );
                }
            }
        }
    }
}

Quando chiamiamo questa funzione? Non c'è modo di sapere con certezza quando viene creato l'oggetto originale. Forse a volte prima 'plugins_loaded'? Forse più tardi?

Usiamo lo stesso hook a cui è associato l'oggetto e saltiamo molto presto con priorità 0. Questo è l'unico modo per essere veramente sicuri. Ecco come rimuoveremo il metodo print_message_3():

add_action( 'wp_footer', 'kill_anonymous_example', 0 );

function kill_anonymous_example()
{
    remove_anonymous_object_filter(
        'wp_footer',
        'Anonymous_Object',
        'print_message_3'
    );
}

Risultato:

inserisci qui la descrizione dell'immagine

E ciò dovrebbe rimuovere l'azione dalla tua domanda (non testata):

add_action( 'comments_array', 'kill_FbComments', 0 );

function kill_FbComments()
{
    remove_anonymous_object_filter(
        'comments_array',
        'SEOFacebookComments',
        'FbComments'
    );
}

Conclusione

  • Scrivi sempre un codice prevedibile. Imposta nomi leggibili per i tuoi filtri e azioni. Semplifica la rimozione di qualsiasi gancio.
  • Crea il tuo oggetto con un'azione prevedibile, ad esempio 'plugins_loaded'. Non solo quando il tuo plugin viene chiamato da WordPress.


@MikeSchinkel Idea correlata , finora non l'ho provato in pratica.
fuxia

Interessante. Trovo la tua risposta molto buona, ma la tua ultima conclusione abbastanza scarsa. A mio avviso, le istanze di classe dovrebbero, in generale, essere istanziate non appena WordPress carica il plugin. Quindi, il costruttore dell'istanza di classe non deve eseguire azioni reali, è sufficiente aggiungere azioni e filtri. In questo modo, i plug-in che desiderano rimuovere azioni e filtri dall'istanza della classe possono essere sicuri che vengano effettivamente aggiunti quando plugins_loadedviene chiamato, il che è esattamente ciò che plugins_loadedserve. Naturalmente, l'istanza di classe deve ancora essere accessibile, possibilmente tramite un modello singleton.
engelen,

@engelen Questa è una vecchia risposta. Oggi vorrei offrire un'azione per rimuovere i callback. Ma non un Singleton, questo è un anti-pattern per molte ragioni.
fuxia

Questa risposta funziona anche per la rimozione di azioni, comeremove_action()
Nick Pyett,

0

Non sono sicuro ma puoi provare a usare un singleton.
È necessario memorizzare il riferimento all'oggetto in una proprietà statica della classe e quindi restituire quella variabile statica da un metodo statico. Qualcosa come questo:

class MyClass{
    private static $ref;
    function MyClass(){
        $ref = &$this;
    }
    public static function getReference(){
        return self::$ref;
    }
}

0

Finché conosci l'oggetto (e usi PHP 5.2 o versioni successive - l'attuale versione stabile di PHP è 5.5, 5.4 è ancora supportata, 5.3 è fine vita), puoi semplicemente rimuoverla con il remove_filter()metodo. Tutto quello che devi ricordare è l'oggetto, il nome metodo e la priorità (se utilizzato):

remove_filter('comment_array', [$this, 'FbComments']);

Tuttavia fai un piccolo errore nel tuo codice. Non $thisaggiungere il prefisso con la e commerciale &, necessaria in PHP 4 (!) Ed è in ritardo da molto tempo. Questo può rendere problematica la gestione dei tuoi ganci, quindi lascialo fuori dai piedi:

add_filter('comments_array', [$this, 'FbComments]));

E questo è tutto.


1
Non hai accesso $thisdall'esterno (un altro plugin / tema).
fuxia
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.