Как удалить фильтр, который является анонимным объектом?


В моем файле functions.php я хотел бы удалить приведенный ниже фильтр, но я не уверен, как это сделать, так как он находится в классе. Как должен выглядеть remove_filter()?

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

Это на линии 88 здесь.

 65
Author: fuxia, 2012-07-01

3 answers

Это очень хороший вопрос. Это относится к темному сердцу API плагинов и лучшим методам программирования.

Для следующего ответа я создал простой плагин, чтобы проиллюстрировать проблему с помощью легко читаемого кода.

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

Теперь мы видим следующее:

enter image description here

WordPress требуется имя для фильтра. Мы его не предоставили, поэтому WordPress звонит _wp_filter_build_unique_id() и создает его. Это имя не предсказуемо, потому что оно использует spl_object_hash().

Если мы запустим var_export() на $GLOBALS['wp_filter'][ 'wp_footer' ] сейчас мы получаем что-то вроде этого:

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

Чтобы найти и удалить наше злое действие, мы должны пройти через соответствующие фильтры для крючка (действие - это просто очень простой фильтр), проверить, является ли это массивом и является ли объект экземпляром класса. Затем мы берем приоритет и удаляем фильтр, , даже не видя реального идентификатора.

Хорошо, давайте поместим это в функция:

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

Когда мы вызываем эту функцию? Невозможно точно узнать, когда будет создан исходный объект. Может быть, иногда раньше 'plugins_loaded'? Может быть, позже?

Мы используем точно такой же крючок, с которым связан объект, и переходим очень рано с приоритетом 0. Это единственный способ быть по-настоящему уверенным. Вот как мы удалили бы этот метод 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'
    );
}

Результат:

enter image description here

И это должно исключить действие из вашего вопроса (не проверено):

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

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

Заключение

  • Всегда пишите предсказуемый код. Задайте понятные имена для ваших фильтров и действий. Облегчите снятие любого крючка.
  • Создайте свой объект на предсказуемом действии, например на 'plugins_loaded'. Не только когда ваш плагин вызывается WordPress.
 84
Author: fuxia, 2013-04-09 03:19:08

Я не уверен, но вы можете попробовать использовать синглтон.
Вы должны сохранить ссылку на объект в статическом свойстве вашего класса, а затем вернуть эту статическую переменную из статического метода. Что-то вроде этого:

class MyClass{
    private static $ref;
    function MyClass(){
        $ref = &$this;
    }
    public static function getReference(){
        return self::$ref;
    }
}
 0
Author: Hamed Momeni, 2012-07-01 10:41:00

Пока вы знаете объект (и вы используете PHP 5.2 или выше - текущая стабильная версия PHP 5.5, 5.4 все еще поддерживается, 5.3 - конец жизни), вы можете просто удалить его с помощью метода remove_filter(). Все, что вам нужно запомнить, это объект, имя метода и приоритет (если используется):

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

Однако вы допускаете небольшую ошибку в своем коде. Не добавляйте префикс $this к амперсанду &, который был необходим в PHP 4 (!), и это давно назрело. Это может помочь справиться с вашими крючками проблематично, так что просто оставьте это в стороне:

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

И это все.

 0
Author: hakre, 2013-07-28 11:59:21