remove_action o remove_filter con clases externas?

55

En una situación en la que un complemento ha encapsulado sus métodos dentro de una clase y luego ha registrado un filtro o acción contra uno de esos métodos, ¿cómo eliminar la acción o el filtro si ya no tiene acceso a la instancia de esa clase?

Por ejemplo, suponga que tiene un complemento que hace esto:

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

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

new MyClass();

Observando que ahora no tengo forma de acceder a la instancia, ¿cómo puedo cancelar el registro de la clase? Este: remove_action( "plugins_loaded", array( MyClass, 'my_action' ) ); no parece ser el enfoque correcto, al menos no parece funcionar en mi caso.

    
pregunta Tom Auger 09.12.2011 - 18:54

5 respuestas

15

Lo mejor que puedes hacer aquí es usar una clase estática. El siguiente código debe ser instructivo:

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

Si ejecuta este código desde un complemento, debería notar que tanto el método de StaticClass como la función se eliminarán de wp_footer.

    
respondido por el mfields 10.12.2011 - 22:25
75

Cuando un complemento crea un new MyClass(); , debe asignarlo a una variable con un nombre único. De esa manera, la instancia de la clase es accesible.

Entonces, si él estaba haciendo $myclass = new MyClass(); , entonces podrías hacer esto:

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

Esto funciona porque los complementos se incluyen en el espacio de nombres global, por lo que las declaraciones de variables implícitas en el cuerpo principal de un complemento son variables globales.

Si el complemento no guarda el identificador de la nueva clase en algún lugar , entonces técnicamente, eso es un error. Uno de los principios generales de la Programación Orientada a Objetos es que los objetos a los que no hace referencia alguna variable en algún lugar están sujetos a limpieza o eliminación.

Ahora, PHP en particular no hace esto como lo haría Java, ya que PHP es más o menos una implementación OOP de media ejecución. Las variables de instancia son solo cadenas con nombres de objetos únicos en ellas, algo así. Solo funcionan debido a la forma en que funciona la interacción variable de nombre de función con el operador -> . Así que solo hacer new class() puede funcionar perfectamente, solo de forma estúpida. :)

Entonces, en conclusión, nunca hagamos new class(); . Haga $var = new class(); y haga que $ var sea accesible de alguna manera para que otros bits lo referencian.

Editar: años más tarde

Una cosa que he visto hacer a muchos complementos es usar algo similar al patrón "Singleton". Crean un método getInstance () para obtener la instancia única de la clase. Esta es probablemente la mejor solución que he visto. Ejemplo de complemento:

class ExamplePlugin
{
    protected static $instance = NULL;

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

La primera vez que se llama a getInstance (), crea una instancia de la clase y guarda su puntero. Puedes usar eso para enganchar acciones.

Un problema con esto es que no puedes usar getInstance () dentro del constructor si usas tal cosa. Esto se debe a que el nuevo constructor llama al constructor antes de establecer la instancia $, por lo que llamar a getInstance () desde el constructor conduce a un bucle infinito y rompe todo.

Una solución alternativa es no usar el constructor (o, al menos, no usar getInstance () dentro de él), sino tener explícitamente una función "init" en la clase para configurar sus acciones y demás. Así:

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

Con algo como esto, al final del archivo, después de que se haya definido la clase y todo lo anterior, crear una instancia del complemento se vuelve tan simple como esto:

ExamplePlugin::init();

Init comienza a agregar tus acciones y, al hacerlo, llama a getInstance (), que crea una instancia de la clase y se asegura de que solo exista una de ellas. Si no tiene una función de inicio, haría esto para crear una instancia de la clase inicialmente:

ExamplePlugin::getInstance();

Para abordar la pregunta original, eliminar ese enlace de acción desde el exterior (también conocido en otro complemento) se puede hacer de la siguiente manera:

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

Coloca eso en algo enganchado al gancho de acción plugins_loaded y deshará la acción enganchada por el complemento original.

    
respondido por el Otto 11.12.2011 - 00:39
12

2 pequeñas funciones de PHP para permitir eliminar filtros / acciones con clase "anónima": enlace

    
respondido por el herewithme 06.11.2012 - 08:55
12

Aquí hay una función ampliamente documentada que creé para eliminar filtros cuando no tienes acceso al objeto de clase (funciona con WordPress 1.2+, incluido 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 );
}
    
respondido por el sMyles 15.09.2016 - 21:58
2

Las soluciones anteriores parecen obsoletas, tuve que escribir mi propia ...

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

Lea otras preguntas en las etiquetas