Adding multiple actions and filters using anonymous functions in WordPress

Using closures when adding hooks and filters in WordPress is very convenient, instead of defining a global function to run your hook, like this:

add_filter('the_content', 'filter_the_content');
function filter_the_content($content)
{
    //Do something with the content
    return $content;
}

You can instead define the function in-place, which is referred to as a closure, or anonymous function:

add_filter('the_content', function($content)
{
    //Do something with the content
    return $content;
});

This is nice because it’s slightly shorter and we avoid polluting the global namespace with functions.

But, you run into a problem if you want to attach the same anonymous function to multiple filters. While you can do it very easily with a global function like this:

//Both the_content and widget_text will be filtered through filter_the_content()
add_filter('the_content', 'filter_the_content');
add_filter('widget_text', 'filter_the_content');

function filter_the_content($content)
{
    //Do something with the content
    return $content;
}

…there’s really no way to do this with anonymous function. You’d have to create the same function twice, resulting in a DRY violation:

//Filter the_content
add_filter('the_content', function($content)
{
    //Do something with the content
    return $content;
});

//Filter widget_text
add_filter('widget_text', function($content)
{
    //Do something with the content
    return $content;
});

That’s not very pretty, so I thought for a while about a good way to attach multiple filters and actions to the same anonymous function.

The solution

By implementing a wrapper function for add_filter(), which we will call add_filters(), we can let developers pass an array as the filter name and then apply the filter function multiple times, each time attaching our anonymous function. If we take the previous example which forced us to repeat ourselves, we can now do this:

add_filters(array( 'the_content', 'widget_text' ), function($content)
{
  //Do something with both the_content and widget_text filters
  return $content;
});

Cool, but what about different priorities?

Let’s fix that too! If you have to apply different priorities, we’ll just specify them as an array in the same order as the array of filter names, like this:

add_filters(array( 'the_content', 'widget_text' ), function($content)
{
  //Do something with both the_content and widget_text filters
  return $content;
},
array(15,20)); //the_content filter will have priority 15 and widget_text filter will have priority 20

How would we do this for actions?

Filters and actions are actually the same thing, the only difference is that an action does not (generally) return anything, it just lets you hook in and run your code at a certain event or timing. (Like when a user registers, when a comment is posted, and so on.) In the WordPress core source code, add_action() is just a wrapper for add_filter()!

For example, this code will print a comment in both the header and footer.

add_actions(array('wp_head', 'wp_footer'), function()                                 
{                                                                                     
    echo "<!-- This text will be printed in both the header and the footer! -->";      
});    

So, how do you get these functions? You can either download the helper plugin on GitHub, or copy and paste the below code into a WordPress theme or plugin!

/**
 * Add multiple filters to a closure
 *
 * @param $tags
 * @param $function_to_add
 * @param int $priority
 * @param int $accepted_args
 *
 * @return bool true
 */
function add_filters($tags, $function_to_add, $priority = 10, $accepted_args = 1)
{
  //If the filter names are not an array, create an array containing one item
  if(!is_array($tags))
    $tags = array($tags);

  //For each filter name
  foreach($tags as $index => $tag)
    add_filter($tag, $function_to_add, (int)(is_array($priority) ? $priority[$index] : $priority), (int)(is_array($accepted_args) ? $accepted_args[$index] : $accepted_args));

  return true;
}

/**
 * Add multiple actions to a closure
 *
 * @param $tags
 * @param $function_to_add
 * @param int $priority
 * @param int $accepted_args
 *
 * @return bool true
 */
function add_actions($tags, $function_to_add, $priority = 10, $accepted_args = 1)
{
  //add_action() is just a wrapper around add_filter(), so we do the same
  return add_filters($tags, $function_to_add, $priority, $accepted_args);
}

Leave a Reply

Your email address will not be published. Required fields are marked *

Markdown is allowed in comments.