remove_action ou remove_filter com classes externas?


59

Em uma situação em que um plug-in encapsulou seus métodos dentro de uma classe e depois registrou um filtro ou ação contra um desses métodos, como você remove a ação ou o filtro se não tem mais acesso à instância dessa classe?

Por exemplo, suponha que você tenha um plugin que faça isso:

class MyClass {
    function __construct() {
       add_action( "plugins_loaded", array( $this, 'my_action' ) );
    }

    function my_action() {
       // do stuff...
    }
}

new MyClass();

Observando que agora não tenho como acessar a instância, como cancelo o registro da classe? Isso: remove_action( "plugins_loaded", array( MyClass, 'my_action' ) );não parece ser a abordagem correta - pelo menos, não parecia funcionar no meu caso.


N / P. Abaixo um trabalho para você?
kaiser

Respostas:


16

A melhor coisa a fazer aqui é usar uma classe estática. O código a seguir deve ser instrutivo:

class MyClass {
    function __construct() {
        add_action( 'wp_footer', array( $this, 'my_action' ) );
    }
    function my_action() {
        print '<h1>' . __class__ . ' - ' . __function__ . '</h1>';
    }
}
new MyClass();


class MyStaticClass {
    public static function init() {
        add_action( 'wp_footer', array( __class__, 'my_action' ) );
    }
    public static function my_action() {
        print '<h1>' . __class__ . ' - ' . __function__ . '</h1>';
    }
}
MyStaticClass::init();

function my_wp_footer() {
    print '<h1>my_wp_footer()</h1>';
}
add_action( 'wp_footer', 'my_wp_footer' );

function mfields_test_remove_actions() {
    remove_action( 'wp_footer', 'my_wp_footer' );
    remove_action( 'wp_footer', array( 'MyClass', 'my_action' ), 10 );
    remove_action( 'wp_footer', array( 'MyStaticClass', 'my_action' ), 10 );
}
add_action( 'wp_head', 'mfields_test_remove_actions' );

Se você executar esse código em um plug-in, observe que o método da StaticClass e a função serão removidos do wp_footer.


7
Ponto considerado, mas nem todas as classes podem simplesmente ser convertidas para serem estáticas.
Geert

Aceitei esta resposta porque ela responde mais diretamente à pergunta, embora a resposta de Otto seja a melhor prática. Observo aqui que não acho que você precise declarar explicitamente estático. Tem sido minha experiência (embora eu possa estar errado) que você pode apenas tratar a função como se fosse uma matriz estática ('MyClass', 'member_function') e geralmente funciona sem a palavra-chave 'static'.
Tom Auger

@ TomAuger não, você não pode, SOMENTE se for adicionado como uma classe estática, você pode usar a remove_actionfunção, caso contrário não funcionará ... é por isso que eu tive que escrever minha própria função para lidar quando não é uma classe estática. Essa resposta só seria o melhor se sua pergunta foi sobre o seu próprio código, caso contrário, você estará tentando remover outro filtro / ação de outra pessoa base de código e não pode mudá-lo para estático
Smyles

78

Sempre que um plug-in cria um new MyClass();, ele deve atribuí-lo a uma variável com nome exclusivo. Dessa forma, a instância da classe é acessível.

Então, se ele estava fazendo $myclass = new MyClass();, então você poderia fazer o seguinte:

global $myclass;
remove_action( 'wp_footer', array( $myclass, 'my_action' ) );

Isso funciona porque os plug-ins estão incluídos no espaço para nome global, portanto, as declarações implícitas de variáveis ​​no corpo principal de um plug-in são variáveis ​​globais.

Se o plugin não salvar o identificador da nova classe em algum lugar , tecnicamente, isso é um bug. Um dos princípios gerais da Programação Orientada a Objetos é que objetos que não estão sendo referenciados por alguma variável em algum lugar estão sujeitos a limpeza ou eliminação.

Agora, o PHP em particular não faz isso como o Java faria, porque o PHP é meio que uma implementação de OOP meio arsada. As variáveis ​​de instância são apenas strings com nomes de objetos exclusivos, algo assim. Eles funcionam apenas devido à maneira como a interação do nome da função variável funciona com o ->operador. Então, apenas fazer new class()pode realmente funcionar perfeitamente, apenas estupidamente. :)

Então, linha de fundo, nunca faça new class();. Faça $var = new class();e torne esse $ var acessível de alguma maneira para outros bits fazerem referência a ele.

Edit: anos depois

Uma coisa que eu já vi muitos plugins fazendo é usar algo semelhante ao padrão "Singleton". Eles criam um método getInstance () para obter a única instância da classe. Esta é provavelmente a melhor solução que eu já vi. Exemplo de plug-in:

class ExamplePlugin
{
    protected static $instance = NULL;

    public static function getInstance() {
        NULL === self::$instance and self::$instance = new self;
        return self::$instance;
    }
}

A primeira vez que getInstance () é chamado, ele instancia a classe e salva seu ponteiro. Você pode usar isso para conectar ações.

Um problema com isso é que você não pode usar getInstance () dentro do construtor se usar isso. Isso ocorre porque o novo chama o construtor antes de definir a instância $, então chamar getInstance () do construtor leva a um loop infinito e quebra tudo.

Uma solução alternativa é não usar o construtor (ou, pelo menos, não usar getInstance () dentro dele), mas ter explicitamente uma função "init" na classe para configurar suas ações e tal. Como isso:

public static function init() {
    add_action( 'wp_footer', array( ExamplePlugin::getInstance(), 'my_action' ) );
}

Com algo assim, no final do arquivo, após a classe ter sido definida e tal, a instanciação do plug-in torna-se tão simples quanto isto:

ExamplePlugin::init();

O Init começa a adicionar suas ações e, ao fazer isso, chama getInstance (), que instancia a classe e garante que apenas uma delas exista. Se você não tiver uma função init, faça isso para instanciar a classe inicialmente:

ExamplePlugin::getInstance();

Para resolver a questão original, a remoção desse gancho de ação de fora (também conhecido como outro plugin) pode ser feita da seguinte maneira:

remove_action( 'wp_footer', array( ExamplePlugin::getInstance(), 'my_action' ) );

Coloque isso em algo ligado ao plugins_loadedgancho de ação e ele desfará a ação que está sendo conectada pelo plug-in original.


3
+1 Tru dat. Essa é claramente uma prática recomendada. Todos nós devemos nos esforçar para escrever nosso código de plugin dessa maneira.
Tom Auger

3
Marcar com +1 essas instruções realmente me ajudou a remover um filtro em uma classe de padrão singleton.
Devin Walker

+1, mas acho que você geralmente deve se conectar a wp_loaded, não plugins_loaded, que pode ser chamado muito cedo.
EML

4
Não, plugins_loadedseria o lugar correto. A wp_loadedação acontece após a initação; portanto, se o seu plug-in executa alguma ação init(e a maioria faz), você deseja inicializar o plug-in e configurá-lo antes disso. O plugins_loadedgancho é o lugar certo para a fase de construção.
Otto

13

2 pequenas funções PHP para permitir a remoção de filtro / ação com a classe "anônimo": https://github.com/herewithme/wp-filters-extras/


Funções muito legais. Obrigado por postar isso aqui!
Tom Auger

Como mencionado haver outras pessoas no meu post abaixo, estes irão quebrar em WordPress 4.7 (a menos que o repo é atualizado, mas não tem em 2 anos)
Smyles

11
Apenas observando que o repositório wp-Filters-extras foi realmente atualizado para a v4.7 e a classe WP_Hook.
Dave Romsey

13

Aqui está uma função amplamente documentada que eu criei para remover filtros quando você não tem acesso ao objeto de classe (funciona com WordPress 1.2+, incluindo 4.7+):

https://gist.github.com/tripflex/c6518efc1753cf2392559866b4bd1a53

/**
 * Remove Class Filter Without Access to Class Object
 *
 * In order to use the core WordPress remove_filter() on a filter added with the callback
 * to a class, you either have to have access to that class object, or it has to be a call
 * to a static method.  This method allows you to remove filters with a callback to a class
 * you don't have access to.
 *
 * Works with WordPress 1.2+ (4.7+ support added 9-19-2016)
 * Updated 2-27-2017 to use internal WordPress removal for 4.7+ (to prevent PHP warnings output)
 *
 * @param string $tag         Filter to remove
 * @param string $class_name  Class name for the filter's callback
 * @param string $method_name Method name for the filter's callback
 * @param int    $priority    Priority of the filter (default 10)
 *
 * @return bool Whether the function is removed.
 */
function remove_class_filter( $tag, $class_name = '', $method_name = '', $priority = 10 ) {
    global $wp_filter;

    // Check that filter actually exists first
    if ( ! isset( $wp_filter[ $tag ] ) ) return FALSE;

    /**
     * If filter config is an object, means we're using WordPress 4.7+ and the config is no longer
     * a simple array, rather it is an object that implements the ArrayAccess interface.
     *
     * To be backwards compatible, we set $callbacks equal to the correct array as a reference (so $wp_filter is updated)
     *
     * @see https://make.wordpress.org/core/2016/09/08/wp_hook-next-generation-actions-and-filters/
     */
    if ( is_object( $wp_filter[ $tag ] ) && isset( $wp_filter[ $tag ]->callbacks ) ) {
        // Create $fob object from filter tag, to use below
        $fob = $wp_filter[ $tag ];
        $callbacks = &$wp_filter[ $tag ]->callbacks;
    } else {
        $callbacks = &$wp_filter[ $tag ];
    }

    // Exit if there aren't any callbacks for specified priority
    if ( ! isset( $callbacks[ $priority ] ) || empty( $callbacks[ $priority ] ) ) return FALSE;

    // Loop through each filter for the specified priority, looking for our class & method
    foreach( (array) $callbacks[ $priority ] as $filter_id => $filter ) {

        // Filter should always be an array - array( $this, 'method' ), if not goto next
        if ( ! isset( $filter[ 'function' ] ) || ! is_array( $filter[ 'function' ] ) ) continue;

        // If first value in array is not an object, it can't be a class
        if ( ! is_object( $filter[ 'function' ][ 0 ] ) ) continue;

        // Method doesn't match the one we're looking for, goto next
        if ( $filter[ 'function' ][ 1 ] !== $method_name ) continue;

        // Method matched, now let's check the Class
        if ( get_class( $filter[ 'function' ][ 0 ] ) === $class_name ) {

            // WordPress 4.7+ use core remove_filter() since we found the class object
            if( isset( $fob ) ){
                // Handles removing filter, reseting callback priority keys mid-iteration, etc.
                $fob->remove_filter( $tag, $filter['function'], $priority );

            } else {
                // Use legacy removal process (pre 4.7)
                unset( $callbacks[ $priority ][ $filter_id ] );
                // and if it was the only filter in that priority, unset that priority
                if ( empty( $callbacks[ $priority ] ) ) {
                    unset( $callbacks[ $priority ] );
                }
                // and if the only filter for that tag, set the tag to an empty array
                if ( empty( $callbacks ) ) {
                    $callbacks = array();
                }
                // Remove this filter from merged_filters, which specifies if filters have been sorted
                unset( $GLOBALS['merged_filters'][ $tag ] );
            }

            return TRUE;
        }
    }

    return FALSE;
}

/**
 * Remove Class Action Without Access to Class Object
 *
 * In order to use the core WordPress remove_action() on an action added with the callback
 * to a class, you either have to have access to that class object, or it has to be a call
 * to a static method.  This method allows you to remove actions with a callback to a class
 * you don't have access to.
 *
 * Works with WordPress 1.2+ (4.7+ support added 9-19-2016)
 *
 * @param string $tag         Action to remove
 * @param string $class_name  Class name for the action's callback
 * @param string $method_name Method name for the action's callback
 * @param int    $priority    Priority of the action (default 10)
 *
 * @return bool               Whether the function is removed.
 */
function remove_class_action( $tag, $class_name = '', $method_name = '', $priority = 10 ) {
    remove_class_filter( $tag, $class_name, $method_name, $priority );
}

2
Pergunta - você testou isso na versão 4.7? Houve algumas alterações na maneira como os retornos de chamada são registrados em filtros novos. Não examinei seu código detalhadamente, mas é algo que você pode querer conferir: make.wordpress.org/core/2016/09/08/…
Tom Auger

sim, tenho certeza de que isso ocorrerá em 4.7
gmazzap

Ahh! Não, eu não fiz, mas obrigado vou def olhar para isso e atualizar esta por isso é compatível (se necessário)
Smyles

11
@ TomAuger obrigado pela atenção! Eu atualizei a função, testou trabalhando em WordPress 4.7+ (com compatibilidade com versões anteriores ainda mantida)
Smyles

11
Acaba de atualizar isso para usar o método de remoção interna do núcleo (para lidar com meados de iteração e evitar avisos PHP)
Smyles

2

As soluções acima parecem desatualizadas, tive que escrever as minhas ...

function remove_class_action ($action,$class,$method) {
    global $wp_filter ;
    if (isset($wp_filter[$action])) {
        $len = strlen($method) ;
        foreach ($wp_filter[$action] as $pri => $actions) {
            foreach ($actions as $name => $def) {
                if (substr($name,-$len) == $method) {
                    if (is_array($def['function'])) {
                        if (get_class($def['function'][0]) == $class) {
                            if (is_object($wp_filter[$action]) && isset($wp_filter[$action]->callbacks)) {
                                unset($wp_filter[$action]->callbacks[$pri][$name]) ;
                            } else {
                                unset($wp_filter[$action][$pri][$name]) ;
                            }
                        }
                    }
                }
            }
        }
    }
}

0

Esta função é baseada na resposta @Digerkam. Adicionado compare if $def['function'][0]é string e finalmente funcionou para mim.

O uso também $wp_filter[$tag]->remove_filter()deve torná-lo mais estável.

function remove_class_action($tag, $class = '', $method, $priority = null) : bool {
    global $wp_filter;
    if (isset($wp_filter[$tag])) {
        $len = strlen($method);

        foreach($wp_filter[$tag] as $_priority => $actions) {

            if ($actions) {
                foreach($actions as $function_key => $data) {

                    if ($data) {
                        if (substr($function_key, -$len) == $method) {

                            if ($class !== '') {
                                $_class = '';
                                if (is_string($data['function'][0])) {
                                    $_class = $data['function'][0];
                                }
                                elseif (is_object($data['function'][0])) {
                                    $_class = get_class($data['function'][0]);
                                }
                                else {
                                    return false;
                                }

                                if ($_class !== '' && $_class == $class) {
                                    if (is_numeric($priority)) {
                                        if ($_priority == $priority) {
                                            //if (isset( $wp_filter->callbacks[$_priority][$function_key])) {}
                                            return $wp_filter[$tag]->remove_filter($tag, $function_key, $_priority);
                                        }
                                    }
                                    else {
                                        return $wp_filter[$tag]->remove_filter($tag, $function_key, $_priority);
                                    }
                                }
                            }
                            else {
                                if (is_numeric($priority)) {
                                    if ($_priority == $priority) {
                                        return $wp_filter[$tag]->remove_filter($tag, $function_key, $_priority);
                                    }
                                }
                                else {
                                    return $wp_filter[$tag]->remove_filter($tag, $function_key, $_priority);
                                }
                            }

                        }
                    }
                }
            }
        }

    }

    return false;
}

Exemplo de uso:

Combinação exata

add_action('plugins_loaded', function() {
    remove_class_action('plugins_loaded', 'MyClass', 'my_action', 0);
});

Qualquer prioridade

add_action('plugins_loaded', function() {
    remove_class_action('plugins_loaded', 'MyClass', 'my_action');
});

Qualquer classe e qualquer prioridade

add_action('plugins_loaded', function() {
    remove_class_action('plugins_loaded', '', 'my_action');
});

0

Esta não é uma resposta genérica, mas uma específica para o tema Avada e o WooCommerce , que acho que outras pessoas podem achar úteis:

function remove_woo_commerce_hooks() {
    global $avada_woocommerce;
    remove_action( 'woocommerce_single_product_summary', array( $avada_woocommerce, 'add_product_border' ), 19 );
}
add_action( 'after_setup_theme', 'remove_woo_commerce_hooks' );
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.