remove_action ou remove_filter com classes externas?

55

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

Por exemplo, suponha que você tenha um plug-in que faça isso:

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

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

new MyClass();

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

    
por Tom Auger 09.12.2011 / 18:54
fonte

5 respostas

15

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 este código a partir de um plugin, você deve notar que o método do StaticClass, assim como a função, serão removidos do wp_footer.

    
por mfields 10.12.2011 / 22:25
fonte
75

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

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

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

Isso funciona porque os plug-ins são incluídos no namespace global, portanto, as declarações de variáveis implícitas 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 erro. 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 o faria, porque o PHP é uma implementação OOP meio arsed. As variáveis de instância são apenas cadeias de caracteres com nomes de objeto exclusivos, tipo de coisa. Eles só funcionam devido à maneira como a interação do nome da função da variável funciona com o operador -> . Então, apenas fazendo new class() pode realmente funcionar perfeitamente, apenas estupidamente. :)

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

Editar: anos depois

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

class ExamplePlugin
{
    protected static $instance = NULL;

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

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

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

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

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, instanciar o plug-in de forma simples:

ExamplePlugin::init();

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

ExamplePlugin::getInstance();

Para resolver a questão original, remover esse gancho de ação do exterior (também conhecido como outro plug-in) pode ser feito da seguinte forma:

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

Coloque isso em algo conectado ao gancho de ação plugins_loaded e isso desfará a ação que está sendo enganchada pelo plug-in original.

    
por Otto 11.12.2011 / 00:39
fonte
12

2 funções PHP pequenas para permitir a remoção de filtro / ação com a classe "anônima": enlace

    
por herewithme 06.11.2012 / 08:55
fonte
12

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

enlace

/**
 * 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 );
}
    
por sMyles 15.09.2016 / 21:58
fonte
2

Acima as soluções parecem desatualizadas, tive que escrever minhas próprias ...

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]) ;
                            }
                        }
                    }
                }
            }
        }
    }
}
    
por Digerkam 25.12.2017 / 10:53
fonte