Como remover um filtro que é um objeto anônimo?

62

No meu functions.phparquivo, eu gostaria de remover o filtro abaixo, mas não sei como fazê-lo, pois está em uma classe. Como deve remove_filter()ser?

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

Está na linha 88 aqui .

Jonas
fonte
Você deve remover o arquivo &do seu &$this, é uma coisa do PHP 4
Tom J Nowell

Respostas:

79

Essa é uma pergunta muito boa. Ele vai para o coração negro da API do plug-in e das melhores práticas de programação.

Para a resposta a seguir, criei um plugin simples para ilustrar o problema com código de fácil leitura.

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

Agora vemos isso:

insira a descrição da imagem aqui

WordPress precisa de um nome para o filtro. Como não fornecemos um, o WordPress chama _wp_filter_build_unique_id()e cria um. Este nome não é previsível porque usa spl_object_hash().

Se executar um var_export()em $GLOBALS['wp_filter'][ 'wp_footer' ]nós obter algo parecido com isso agora:

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

Para encontrar e remover nossa ação maligna, precisamos passar pelos filtros associados ao gancho (uma ação é apenas um filtro muito simples), verifique se é uma matriz e se o objeto é uma instância da classe. Em seguida, tomamos a prioridade e removemos o filtro, sem nunca ver o identificador real .

Ok, vamos colocar isso em uma função:

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 chamamos essa função? Não há como saber com certeza quando o objeto original é criado. Talvez algumas vezes antes 'plugins_loaded'? Talvez mais tarde?

Usamos o mesmo gancho ao qual o objeto está associado e saltamos muito cedo com prioridade 0. Essa é a única maneira de ter certeza. Aqui está como removeríamos o método 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'
    );
}

Resultado:

insira a descrição da imagem aqui

E isso deve remover a ação da sua pergunta (não testada):

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

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

Conclusão

  • Sempre escreva código previsível. Defina nomes legíveis para seus filtros e ações. Facilite a remoção de qualquer gancho.
  • Crie seu objeto em uma ação previsível, por exemplo, em 'plugins_loaded'. Não apenas quando o seu plugin é chamado pelo WordPress.
fuxia
fonte
FYI
MikeSchinkel #
@ MikeSchinkel Idéia relacionada , ainda não tentei na prática até agora.
Fuxia
Interessante. Acho sua resposta muito boa, mas sua última conclusão é muito ruim. Na minha opinião, as instâncias de classe devem, em geral, ser instanciadas assim que o WordPress carregar o plugin. Então, o construtor da instância da classe não deve executar nenhuma ação real, basta adicionar ações e filtros. Dessa forma, os plug-ins que desejam remover ações e filtros da sua instância de classe podem ter certeza de que eles são realmente adicionados quando plugins_loadedchamados, o que é exatamente plugins_loadedpara isso. Obviamente, a instância da classe ainda precisa estar acessível, possivelmente via padrão singleton.
Engelen
@engelen Esta é uma resposta antiga. Atualmente, eu ofereceria uma ação para remover os retornos de chamada. Mas não um Singleton, isso é um anti-padrão por muitas razões.
fuxia
Esta resposta também funciona para remover ações, comoremove_action()
Nick Pyett
0

Não tenho certeza, mas você pode tentar usar um singleton.
Você deve armazenar a referência do objeto em uma propriedade estática da sua classe e retornar essa variável estática de um método estático. Algo assim:

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

Contanto que você conheça o objeto (e use o PHP 5.2 ou superior - a versão estável do PHP atual é 5.5, 5.4 ainda é suportada, 5.3 é o fim da vida útil), você pode simplesmente removê-lo com o remove_filter()método Tudo o que você precisa lembrar é o objeto, o nome do método e a prioridade (se usada):

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

No entanto, você comete um pequeno erro no seu código. Não prefixe $thiso e comercial &, que era necessário no PHP 4 (!) E está muito atrasado. Isso pode tornar problemático lidar com seus ganchos, então deixe-o fora do caminho:

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

E é isso.

hakre
fonte
11
Você não tem acesso $thisexterno (outro plugin / tema).
fuxia