How to remove a filter that is an object method?

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.

Related posts

Leave a Reply

3 comments

  1. 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.
  2. 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.

  3. 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;
        }
    }