Jak usunąć filtr, który jest anonimowym obiektem?


62

W moim functions.phppliku chciałbym usunąć poniższy filtr, ale nie jestem pewien, jak to zrobić, ponieważ jest w klasie. Jak powinien remove_filter()wyglądać

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

Jest na linii 88 tutaj .


Powinieneś usunąć &z &$this, to jest PHP 4
Tom J Nowell

Odpowiedzi:


79

To bardzo dobre pytanie. Wszystko sprowadza się do mrocznego serca interfejsu API wtyczki i najlepszych praktyk programistycznych.

Dla poniższej odpowiedzi stworzyłem prostą wtyczkę ilustrującą problem z łatwym do odczytania kodem.

<?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;
}

Teraz widzimy to:

wprowadź opis zdjęcia tutaj

WordPress potrzebuje nazwy filtra. Nie dostarczyliśmy jednego, więc WordPress wywołuje _wp_filter_build_unique_id()i tworzy jeden. Ta nazwa nie jest przewidywalna, ponieważ używa spl_object_hash().

Jeśli prowadzimy var_export()na $GLOBALS['wp_filter'][ 'wp_footer' ]Dostajemy teraz coś takiego:

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,
    ),
  ),
)

Aby znaleźć i usunąć naszą złą akcję, musimy przejść przez powiązane filtry przechwytujące (akcja jest po prostu bardzo prostym filtrem), sprawdzić, czy jest to tablica i czy obiekt jest instancją klasy. Następnie bierzemy priorytet i wyjąć filtr, nigdy nie widząc prawdziwego identyfikatora .

Ok, umieśćmy to w funkcji:

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

Kiedy wywołujemy tę funkcję? Nie ma pewności, kiedy powstanie oryginalny obiekt. Może czasami wcześniej 'plugins_loaded'? Może później?

Używamy tego samego haka, z którym jest powiązany obiekt, i wskakujemy bardzo wcześnie z priorytetem 0. To jedyny sposób, aby być naprawdę pewnym. Oto jak usunęlibyśmy metodę 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'
    );
}

Wynik:

wprowadź opis zdjęcia tutaj

I to powinno usunąć działanie z twojego pytania (nie przetestowane):

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

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

Wniosek

  • Zawsze pisz przewidywalny kod. Ustaw czytelne nazwy dla swoich filtrów i działań. Ułatw usunięcie haka.
  • Utwórz obiekt na przewidywalnej akcji, na przykład na 'plugins_loaded'. Nie tylko wtedy, gdy wtyczka jest wywoływana przez WordPress.


@MikeSchinkel Powiązany pomysł , jak dotąd nie wypróbowałem go w praktyce.
fuxia

Ciekawy. Uważam twoją odpowiedź za bardzo dobrą, ale twój ostatni wniosek jest dość kiepski. Moim zdaniem instancje klas powinny być generowane natychmiast po załadowaniu wtyczki przez WordPress. Następnie konstruktor instancji klasy nie powinien wykonywać żadnych rzeczywistych akcji, wystarczy dodać akcje i filtry. W ten sposób wtyczki, które chcą usunąć akcje i filtry z instancji klasy, mogą mieć pewność, że zostaną faktycznie dodane po plugins_loadedwywołaniu, co jest właśnie po to plugins_loaded. Oczywiście instancja klasy nadal musi być dostępna, być może poprzez wzorzec singletonu.
engelen

@engelen To jest stara odpowiedź. Obecnie oferowałbym akcję usuwania oddzwaniania. Ale nie Singleton, to anty-wzór z wielu powodów.
fuxia

Ta odpowiedź działa również w przypadku usuwania akcji, takich jakremove_action()
Nick Pyett

0

Nie jestem pewien, ale możesz spróbować użyć singletona.
Musisz zapisać odwołanie do obiektu we właściwości statycznej swojej klasy, a następnie zwrócić tę zmienną statyczną z metody statycznej. Coś takiego:

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

0

Tak długo, jak znasz ten obiekt (i używasz PHP 5.2 lub nowszego - aktualna stabilna wersja PHP to 5.5, 5.4 jest nadal obsługiwany, 5.3 to koniec życia), możesz po prostu usunąć go za pomocą remove_filter()metody. Wszystko, co musisz zapamiętać, to obiekt, nazwa metody i priorytet (jeśli jest używany):

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

Jednak popełniacie mały błąd w kodzie. Nie prefiks $thisze znakiem ampersand &, który był potrzebny w PHP 4 (!), I jest to od dawna spóźnione. Może to sprawić, że radzenie sobie z hakami będzie problematyczne, więc po prostu odsuń się na bok:

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

I to wszystko.


1
Nie masz dostępu $thisz zewnątrz (inna wtyczka / motyw).
fuxia
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.