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

62

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

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

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

Jonas
источник
Вы должны удалить &из своего &$this, это PHP 4 вещь
Том Дж. Новелл

Ответы:

79

Это очень хороший вопрос. Это относится к темному сердцу 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;
}

Теперь мы видим это:

введите описание изображения здесь

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

Результат:

введите описание изображения здесь

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

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

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

Заключение

  • Всегда пишите предсказуемый код. Установите удобочитаемые имена для ваших фильтров и действий. Сделать это легко, чтобы удалить любой крюк.
  • Создайте свой объект на предсказуемом действии, например, на 'plugins_loaded'. Не только когда ваш плагин вызывается из WordPress.
Фуксия
источник
@MikeSchinkel Похожая идея , пока не пробовал на практике.
fuxia
Интересно. Я нахожу ваш ответ очень хорошим, но ваш последний вывод довольно плохим. По моему мнению, экземпляры классов, как правило, должны создаваться, как только WordPress загружает плагин. Тогда конструктор экземпляра класса не должен выполнять никаких реальных действий, просто добавлять действия и фильтры. Таким образом, плагины, которые хотят удалить действия и фильтры из экземпляра вашего класса, могут быть уверены, что они действительно добавляются при plugins_loadedвызове, и это именно то plugins_loaded, для чего. Конечно, экземпляр класса все еще должен быть доступен, возможно, через шаблон синглтона.
engelen
@engelen Это старый ответ. В настоящее время я бы предложил действие по удалению обратных вызовов. Но не синглтон, это анти-шаблон по многим причинам.
fuxia
Этот ответ также работает для удаления действий, таких какremove_action()
Ник Питт
0

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

class MyClass{
    private static $ref;
    function MyClass(){
        $ref = &$this;
    }
    public static function getReference(){
        return self::$ref;
    }
}
Хамед Момени
источник
0

Если вы знаете объект (и используете 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]));

Вот и все.

hakre
источник
1
У вас нет доступа $thisизвне (другой плагин / тема).
fuxia