Як видалити фільтр, який є анонімним об’єктом?


62

У своєму functions.phpфайлі я хотів би видалити нижній фільтр, але я не знаю, як це зробити, оскільки він знаходиться в класі. Як має remove_filter()виглядати?

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

Це на лінії 88 тут .


Ви повинні вилучити &зі свого &$this, це PHP 4
Tom J Nowell

Відповіді:


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()
Nick Pyett

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разом із символом ampersand &, що було потрібно в PHP 4 (!), І це давно назріло. Це може зробити проблему з гачками проблематичною, тому просто не залиште це:

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

І це все.


1
У вас немає доступу $thisззовні (інший плагін / тема).
fuxia
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.