Comment supprimer un filtre qui est un objet anonyme?

62

Dans mon functions.phpfichier, j'aimerais supprimer le filtre ci-dessous, mais je ne sais pas comment le faire car il est dans une classe. À quoi devrait remove_filter()ressembler?

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

C'est à la ligne 88 ici .

Jonas
la source
Vous devriez enlever le &de votre &$this, c'est une chose de PHP 4
Tom J Nowell

Réponses:

79

C'est une très bonne question. Cela va au cœur sombre de l'API du plugin et aux meilleures pratiques de programmation.

Pour la réponse suivante, j'ai créé un plugin simple pour illustrer le problème avec du code facile à lire.

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

Maintenant nous voyons ceci:

entrez la description de l'image ici

WordPress a besoin d'un nom pour le filtre. Nous n'en avons pas fourni, donc WordPress appelle _wp_filter_build_unique_id()et en crée un. Ce nom n'est pas prévisible car il utilise spl_object_hash().

Si nous courons un var_export()sur $GLOBALS['wp_filter'][ 'wp_footer' ]nous obtenons quelque chose comme ça maintenant:

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

Pour trouver et supprimer notre action maléfique, nous devons passer en revue les filtres associés au point d'ancrage (une action est simplement un filtre très simple), vérifiez si c'est un tableau et si l'objet est une instance de la classe. Ensuite, nous prenons la priorité et retirons le filtre, sans jamais voir l'identifiant réel .

Ok, mettons cela dans une fonction:

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

Quand appelons-nous cette fonction? Il n'y a aucun moyen de savoir avec certitude quand l'objet original est créé. Peut-être parfois avant 'plugins_loaded'? Peut-être plus tard?

Nous utilisons le même point auquel l'objet est associé et nous y allons très tôt avec priorité 0. C'est le seul moyen d'être vraiment sûr. Voici comment nous supprimerions la méthode 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'
    );
}

Résultat:

entrez la description de l'image ici

Et cela devrait supprimer l'action de votre question (non testée):

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

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

Conclusion

  • Écrivez toujours du code prévisible. Définissez des noms lisibles pour vos filtres et vos actions. Rendez-le facile à enlever n'importe quel crochet.
  • Créez votre objet sur une action prévisible, par exemple sur 'plugins_loaded'. Pas seulement lorsque votre plugin est appelé par WordPress.
fuxia
la source
FYI
MikeSchinkel
@MikeSchinkel Idée associée , je ne l'ai pas encore essayée dans la pratique.
fuxia
Intéressant. Je trouve votre réponse très bonne, mais votre dernière conclusion assez médiocre. À mon avis, les instances de classe devraient, en général, être instanciées dès que WordPress charge le plugin. Ensuite, le constructeur de l'instance de classe ne doit pas effectuer d'actions réelles, mais simplement ajouter des actions et des filtres. De cette façon, les plugins qui souhaitent supprimer des actions et des filtres de votre instance de classe peuvent être sûrs qu’ils sont réellement ajoutés lorsqu’on plugins_loadedappelle, ce qui est exactement ce à quoi ils servent plugins_loaded. Bien entendu, l’instance de classe doit toujours être accessible, éventuellement via un modèle singleton.
Engelen
@engelen C'est une vieille réponse. De nos jours, je propose une action pour supprimer les rappels. Mais pas un Singleton, c'est un anti-modèle pour de nombreuses raisons.
fuxia
Cette réponse fonctionne également pour supprimer des actions, telles queremove_action()
Nick Pyett le
0

Je ne suis pas sûr, mais vous pouvez utiliser un singleton.
Vous devez stocker la référence de l'objet dans une propriété statique de votre classe, puis renvoyer cette variable statique à partir d'une méthode statique. Quelque chose comme ça:

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

Tant que vous connaissez l'objet (et que vous utilisez PHP 5.2 ou une version ultérieure - la version stable actuelle de PHP est 5.5, la version 5.4 est toujours prise en charge, la version 5.3 est en fin de vie), vous pouvez simplement le supprimer à l'aide de la remove_filter()méthode. Tout ce dont vous devez vous souvenir est l'objet, le nom de la méthode et la priorité (si utilisée):

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

Cependant, vous faites une petite erreur dans votre code. Ne préfixez pas $thisl'esperluette &, cela était nécessaire en PHP 4 (!) Et il est en retard depuis longtemps. Cela peut rendre la gestion de vos hooks problématique, alors laissez-le de côté:

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

Et c'est tout.

hakre
la source
1
Vous n'avez pas accès à $thisde l'extérieur (un autre plugin / thème).
fuxia