Take the 2-minute tour ×
WordPress Development Stack Exchange is a question and answer site for WordPress developers and administrators. It's 100% free, no registration required.

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.

share|improve this question
    
You should remove the & from your &$this, it's a PHP 4 thing –  Tom J Nowell Mar 22 at 15:26
add comment

3 Answers

up vote 34 down vote accepted

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.
share|improve this answer
9  
+1 the dark heart of the plug-in API –  Stephen Harris Jul 1 '12 at 13:53
    
@toscho - Brilliant, great work. But I think there is a tiny error in the code for remove_anonymous_object_filter() on the line that contains and $method === $function['function'][5]; shouldn't it be [1] instead of [5]? –  MikeSchinkel Apr 9 '13 at 2:33
    
@MikeSchinkel Indeed. No idea how this happened, in my local code this is 1. :) –  toscho Apr 9 '13 at 3:20
    
@toscho - It happens. :) –  MikeSchinkel Apr 9 '13 at 3:51
    
@toscho FYI –  MikeSchinkel Apr 9 '13 at 3:57
show 2 more comments

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;
    }
}
share|improve this answer
add comment

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.

share|improve this answer
    
You have no access to $this from the outside (another plugin/theme). –  toscho Jul 28 '13 at 12:03
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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