How To Remove Anonymous Object And Anonymous Function Hooks

If you’re at all familiar with WordPress development, you already know how to remove a normal hook: just call remove_action or remove_filter and pass it the hook name, the name of the hook callback you want to remove and the priority. But what if the callback has no fixed name because it’s an anonymous function (a.k.a closure) or a method of an anonymous object?

In this post I will show you how to remove anonymous function and anonymous object instance hooks.

Anonymous Objects

Lets look at removing hooks related to anonymous objects first. A plugin might register one like this:

class MyExamplePlugin {
	function __construct() {
		add_action('admin_notices', array($this, 'removeMe'));
	}

	function removeMe() {
		echo '<div class="updated"><p>Anonymous object</p></div>';
	}
}

new MyExamplePlugin;

The above example will output this message in the WordPress Dashboard:

Normally, when you register an instance method as a hook callback, you can remove it by calling remove_filter('hook', array($instance, 'methodName'). In this case we can’t get a hold of the anonymous MyExamplePlugin instance from outside the class, so that won’t work.

To remove this filter, we’ll need to check all callbacks associated with the hook, and find the one that is an array containing an instance of the class we’re looking for and the right method name. Then we can remove it by passing the matching callback to remove_filter.

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
	 * @param int    $priority Optional. Hook priority. Defaults to 10.
	 * @return bool
	 */
    function remove_anonymous_object_filter($tag, $class, $method, $priority = 10) {
		if ( !isset($GLOBALS['wp_filter'][$tag][$priority]) ) {
			return false;
		}
        $filters = $GLOBALS['wp_filter'][$tag][$priority];

		foreach ($filters as $callback) {
			if ( is_array($callback['function'])
				&& is_a( $callback['function'][0], $class )
				&& $method === $callback['function'][1]
			) {
				return remove_filter($tag, $callback['function'], $priority);
			}
		}

		return false;
    }
}

Here’s how we can use this function to remove the example hook:

function ws_remove_example_hook() {
	remove_anonymous_object_filter(
		'admin_notices',
		'MyExamplePlugin',
		'removeMe'
	);
}
add_action('plugins_loaded', 'ws_remove_example_hook');

Credit: WordPress StackExchange – How to remove a filter that is an anonymous object? by toscho

Closures and create_function()

We’ve dealt with anonymous object instances. How about anonymous functions? As of PHP 5.3, there are two ways you can create an anonymous function – create_function() and closures. Here’s an example plugin that uses both:

class MyExamplePlugin {
	function __construct() {
		add_action(
			'admin_notices',
			create_function(
				'', 
				'echo \'<div class="updated"><p>create_function()</p></div>\';'
			),
			11
		);

		add_action(
			'admin_notices',
			function() {
				echo '<div class="updated"><p>Closure</p></div>';
			},
			12
		);
	}
}
new MyExamplePlugin;

Output:

To remove an anonymous function filter, we’ll need to again go through all the callbacks registered for the hook to find the one we want to remove. To identify the right callback, we’ll use reflection to get the name of the file where the function was defined and compare it with the plugin’s filename.

The process is the same for closures and anonymous functions created with create_function(), with one exception: in the case of create_function(), the string returned by ReflectionFunction::getFileName() will contain not just the file name, but also the line number of the create_function() call and the string “: runtime-created function”. We’ll need to get rid of these superfluous bits before comparing the filenames.

if ( !function_exists('remove_anonymous_function_filter') ) {
	/**
	 * Remove an anonymous function filter.
	 *
	 * @param string $tag      Hook name.
	 * @param string $filename The file where the function was declared.
	 * @param int $priority    Optional. Hook priority. Defaults to 10.
	 * @return bool
	 */
	function remove_anonymous_function_filter($tag, $filename, $priority = 10) {
		$filename = plugin_basename($filename);

		if ( !isset($GLOBALS['wp_filter'][$tag][$priority]) ) {
			return false;
		}
		$filters = $GLOBALS['wp_filter'][$tag][$priority];

		foreach ($filters as $callback) {
			if ( ($callback['function'] instanceof Closure) || is_string($callback['function']) ) {
				$function = new ReflectionFunction($callback['function']);

				$funcFilename = plugin_basename($function->getFileName());
				$funcFilename = preg_replace('@\(\d+\)\s+:\s+runtime-created\s+function$@', '', $funcFilename);

				if ( $filename === $funcFilename ) {
					return remove_filter($tag, $callback['function'], $priority);
				}
			}
		}

		return false;
	}
}

For example, if the MyExamplePlugin class lives in /wp-content/plugins/example-plugin/plugin.php, we can remove the two sample callbacks like this:

function ws_remove_anonymous_hooks() {
	remove_anonymous_function_filter(
		'admin_notices',
		'example-plugin/plugin.php',
		11
	);

	remove_anonymous_function_filter(
		'admin_notices',
		'example-plugin/plugin.php',
		12
	);
}
add_action('plugins_loaded', 'ws_remove_anonymous_hooks');
Related posts :

One Response to “How To Remove Anonymous Object And Anonymous Function Hooks”

  1. AnyDog says:

    So …
    after two years this article has a reader that made quite a good use of this code. Thank you very much, you saved me !!

    : )
    Cheers

Leave a Reply