67

In my functions.php file I would like to remove the below filter, but I'm not sure how to do it since it's in a class. What should remove_filter() look like?

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

It's on line 88 here.

1
  • 1
    You should remove the & from your &$this, it's a PHP 4 thing
    – Tom J Nowell
    Commented Mar 22, 2014 at 15:26

3 Answers 3

91

That’s a very good question. It goes down to the dark heart of the plugin API and best programming practices.

For the following answer I created a simple plugin to illustrate the problem with easy to read code.

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

Now we see this:

enter image description here

WordPress needs a name for the filter. We didn’t provide one, so WordPress calls _wp_filter_build_unique_id() and creates one. This name is not predictable because it uses spl_object_hash().

If we run a var_export() on $GLOBALS['wp_filter'][ 'wp_footer' ] we get something like this now:

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

To find and remove our evil action we have to go through the associated filters for the hook (an action is just a very simple filter), check if it is an array and if the object is an instance of the class. Then we take the priority and remove the filter, without ever seeing the real identifier.

Okay, let’s put that into a function:

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

When do we call this function? There is no way to know for sure when the original object is created. Maybe sometimes before 'plugins_loaded'? Maybe later?

We use just the same hook the object is associated to and jump in very early with priority 0. That’s the only way to be really sure. Here is how we would remove the method 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'
    );
}

Result:

enter image description here

And that should remove the action from your question (not tested):

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

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

Conclusion

  • Always write predictable code. Set readable names for your filters and actions. Make it easy to remove any hook.
  • Create your object on a predictable action, for example on 'plugins_loaded'. Not just when your plugin is called by WordPress.
12
  • FYI Commented Apr 9, 2013 at 3:57
  • @MikeSchinkel Related idea, haven't tried it in practice so far.
    – fuxia
    Commented Apr 9, 2013 at 4:02
  • Interesting. I find your answer very good, but your last conclusion quite poor. In my opinion, class instances should, in general, be instantiated as soon as WordPress loads the plugin. Then, the constructor of the class instance shouldn't perform any real actions, just add actions and filters. This way, plugins that wish to remove actions and filters from your class instance can be sure that they are actually added when plugins_loaded is called, which is exactly what plugins_loaded is for. Of course, the class instance still needs to be accessible, possibly via singleton pattern.
    – engelen
    Commented Jun 20, 2014 at 14:20
  • @engelen This is an old answer. Nowadays I would offer an action to remove the callbacks. But not a Singleton, that’s an anti-pattern for many reasons.
    – fuxia
    Commented Jun 20, 2014 at 17:16
  • 1
    I am getting this error when trying to remove action from an anonymous class: "Uncaught Error: Cannot use object of type Closure as array". I fixed it by adding this and condition inside the condition of second foreach loop: "and is_array( $function['function'])" Commented Aug 20, 2022 at 8:28
0

As long as you know the object (and you use PHP 5.2 or higher - current stable PHP version is 5.5, 5.4 is still supported, 5.3 is end of life), you can just remove it with the remove_filter() method. All you need to remember is the object, the method-name and the priority (if used):

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

However you do a little mistake in your code. Don't prefix $this with the ampersand &, that was needed in PHP 4 (!) and it's long-time overdue. This can render dealing with your hooks problematic, so just leave it out of the way:

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

And that's it.

2
  • 3
    You have no access to $this from the outside (another plugin/theme).
    – fuxia
    Commented Jul 28, 2013 at 12:03
  • @fuxia: yes sure, but that should be not a problem right, because you can use another variable with that object. it is only that for the brevity of the example that with $this should be clear which object that is (and you probably just have a plugin/module class that always has $this so can manage itself).
    – hakre
    Commented Dec 13, 2022 at 10:01
0

I'm not sure but you can try using a singleton.
You must store the object reference in a static property of your class and then return that static variable from a static method. Something like this:

class MyClass{
    private static $ref;
    function MyClass(){
        $ref = $this;
    }
    public static function getReference(){
        return self::$ref;
    }
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.