¿Cómo eliminar un filtro que es un objeto anónimo?

62

En mi functions.phparchivo, me gustaría eliminar el filtro a continuación, pero no estoy seguro de cómo hacerlo, ya que está en una clase. ¿Cómo debería remove_filter()ser?

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

Está en la línea 88 aquí .

Jonas
fuente
Debe eliminar el &de su &$this, es una cosa de PHP 4
Tom J Nowell

Respuestas:

79

Esa es una muy buena pregunta. Se reduce al corazón oscuro de la API del complemento y las mejores prácticas de programación.

Para la siguiente respuesta, creé un complemento simple para ilustrar el problema con un código fácil de leer.

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

Ahora vemos esto:

ingrese la descripción de la imagen aquí

WordPress necesita un nombre para el filtro. No proporcionamos uno, por lo que WordPress llama _wp_filter_build_unique_id()y crea uno. Este nombre no es predecible porque lo usa spl_object_hash().

Si ejecutamos un var_export()encendido $GLOBALS['wp_filter'][ 'wp_footer' ], obtenemos algo como esto ahora:

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 y eliminar nuestra acción maligna, tenemos que pasar por los filtros asociados para el gancho (una acción es solo un filtro muy simple), verifique si es una matriz y si el objeto es una instancia de la clase. Luego tomamos la prioridad y eliminamos el filtro, sin ver nunca el identificador real .

Bien, pongamos eso en una función:

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

¿Cuándo llamamos a esta función? No hay forma de saber con certeza cuándo se crea el objeto original. Tal vez a veces antes 'plugins_loaded'? ¿Quizas mas tarde?

Usamos el mismo gancho al que está asociado el objeto y saltamos muy temprano con prioridad 0. Esa es la única manera de estar realmente seguro. Así es como eliminaríamos el 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:

ingrese la descripción de la imagen aquí

Y eso debería eliminar la acción de su pregunta (no probada):

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

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

Conclusión

  • Siempre escriba código predecible. Establezca nombres legibles para sus filtros y acciones. Facilite la extracción de cualquier gancho.
  • Cree su objeto en una acción predecible, por ejemplo, en 'plugins_loaded'. No solo cuando WordPress llama a tu complemento.
fuxia
fuente
FYI
MikeSchinkel
@MikeSchinkel Idea relacionada , hasta ahora no lo he probado en la práctica.
fuxia
Interesante. Su respuesta me parece muy buena, pero su última conclusión es bastante pobre. En mi opinión, las instancias de clase deberían, en general, instanciarse tan pronto como WordPress cargue el complemento. Entonces, el constructor de la instancia de clase no debe realizar ninguna acción real, solo agregar acciones y filtros. De esta manera, los complementos que desean eliminar acciones y filtros de su instancia de clase pueden estar seguros de que realmente se agregan cuando plugins_loadedse llama, que es exactamente para lo que plugins_loadedsirve. Por supuesto, la instancia de clase aún debe ser accesible, posiblemente a través de un patrón singleton.
engelen
@engelen Esta es una vieja respuesta. Hoy en día ofrecería una acción para eliminar las devoluciones de llamada. Pero no es un Singleton, eso es un antipatrón por muchas razones.
fuxia
Esta respuesta también funciona para eliminar acciones, comoremove_action()
Nick Pyett
0

No estoy seguro, pero puedes intentar usar un singleton.
Debe almacenar la referencia del objeto en una propiedad estática de su clase y luego devolver esa variable estática de un método estático. Algo como esto:

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

Siempre que conozca el objeto (y use PHP 5.2 o superior; la versión PHP estable actual es 5.5, 5.4 todavía es compatible, 5.3 es el final de la vida útil), puede eliminarlo con el remove_filter()método. Todo lo que necesita recordar es el objeto, el nombre del método y la prioridad (si se usa):

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

Sin embargo, cometes un pequeño error en tu código. No coloque $thisel prefijo con el ampersand &, eso era necesario en PHP 4 (!) Y está retrasado desde hace mucho tiempo. Esto puede hacer que lidiar con tus ganchos sea problemático, así que déjalo de lado:

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

Y eso es.

hakre
fuente
1
No tiene acceso $thisdesde el exterior (otro complemento / tema).
fuxia