Advertisement

Reel 'em In: Understanding Hooks From the Inside Out

by

This Cyber Monday Tuts+ courses will be reduced to just $3 (usually $15). Don't miss out.

The key to WordPress' flexible foundation is in its use of hooks. Be it in themes, plugins, or the core, hooks allow unparalleled growth while ensuring compatibility with future versions of WordPress. As a result, understanding them should undoubtedly be a part of any developer's repertoire. Join in as we uncover the intricacies of this simple yet sophisticated system from the inside out.

As a roadmap for this article, we'll be going over the concept behind hooks, their implementation, and, of course, examples.


Concept: What and Why?

Hooks

"Put simply, a hook is a placeholder for an action."

Behind any well-written software is a solid concept that answers the questions of what and why. Hooks are no exception. Put simply, a hook is a placeholder for an action. When a significant event occurs, such as the publishing of a post, a hook is activated, allowing relevant reactions to take place. In developer terms, one can think of hooks as the implementation of an event-driven system.

More than just a definition, a concept requires reasoning—backing that elucidates why it is useful. Hooks play an integral role in WordPress because of the project's ever-evolving nature. With hundreds of developers constantly updating the system, it is not possible to edit the core files for each plugin, theme, or special customization, as these will frequently change. Instead, a framework is necessary to extend the system—to allow external functionality to have as much effect as internal manipulations. Hooks are the key to this framework.


Implementation: How?

As a developer, one's role is extended to not only understand what something does or why it is done, but also to realize how it is created. In other words, to fully understand the system of hooks, one must understand how they are implemented.

Just like in a multiple-choice test, though, looking directly at the answers first isn't necessarily the best idea. As many test-taking strategies will suggest, it's often better to read the question, formulate your own thoughts about the answer, and then pick the choice that most closely resembles your reasoning. A similar method can be put into effect when understanding software development implementations; rather than looking at someone else's code to understand how a feature is realized, it is often even more useful to first implement it yourself and then return to examine how it is actually done. This is exactly what we'll do.

Rather than looking at someone else's code to understand how a feature is realized, it is often even more useful to first implement it yourself and then return to examine how it is actually done.

Inferring from the Documentation

To gain insights on how a system is implemented, documentation is often a useful start. Let's examine the summaries of the two core WordPress hook functions as per the Codex:

  • add_action( $hook, $function [, $priority [, $numArgs ] ] ) - Specifies a function handler, $function, to be called once a certain hook, $hook, is activated due to an event's occurrence. $priority determines whether this function handler is called before or after other function handlers and defaults to 10. Lower priorities result in a function being called earlier and vice-versa. $numArgs is the number of arguments the function handler will take and defaults to 1.
  • do_action( $hook [, $arg1 [, $arg2 [, $arg3 [, ... ] ] ] ] ) - Activates a certain hook, $hook, by calling all handling functions with the optional arguments $arg1, $arg2, $arg3, etc.

With the documentation in hand, it's time to make some inferences about these two functions. add_action merely needs to associate a function, priority, and number of arguments with a string. This seems like the perfect job for a PHP array, which dually acts as a hash map that stores key-value pairs. do_action is even more trivial, requiring a simple look up into this same array to find the corresponding function handlers and subsequently calling them. With our insights in mind, it's time to move on to our implementation.

Applying Our Knowledge

Knowledge

Because we're doing this to gain insights into the WordPress hook system, there's no need to implement these two functions verbatim from the documentation. Instead, let's focus on their implementations without the optional arguments to save time and still get the gist of it.

Before we begin, let's lay out an outline:

  • We'll need a global array that is accessible to both functions.
  • add_action will store the given hook name and a set of corresponding function handlers as a key-value pair into the array.
  • do_action will retrieve the corresponding function handlers for a given hook name and call each one.

This yields the following code:

$actions = array();

function add_action( $hook, $function )
{
    global $actions;

    // create an array of function handlers if it doesn't already exist
    if( !isset( $actions[ $hook ] ) )
        $actions[ $hook ] = array();

    // append the current function to the list of function handlers
    $actions[ $hook ][] = $function;
}

function do_action( $hook )
{
    global $actions;

    if( isset( $actions[ $hook ] ) )
    {
        // call each function handler associated with this hook
        foreach( $actions[ $hook ] as $function )
            call_user_func( $function );
    }
}

That's not bad; we implemented a versatile hook system in around 20 lines of code. Now that we've garnered an idea of how hooks work, let's dive into the WordPress core code to confirm our hypothesis.

A tool to quickly navigate through this code is Yoast's PHP Cross Reference of the WordPress Source. Searching for add_action yields the following code:

function add_action($tag, $function_to_add, $priority = 10, $accepted_args = 1) {
    return add_filter($tag, $function_to_add, $priority, $accepted_args);
}

function add_filter($tag, $function_to_add, $priority = 10, $accepted_args = 1) {
    global $wp_filter, $merged_filters;

    $idx = _wp_filter_build_unique_id($tag, $function_to_add, $priority);
    $wp_filter[$tag][$priority][$idx] = array('function' => $function_to_add, 'accepted_args' => $accepted_args);
    unset( $merged_filters[ $tag ] );
    return true;
}

add_action calls add_filter which, unsurprisingly, adds the given function to an array keyed by the hook name, $tag. Although there is a bit more involved due to the $priority and $accepted_args parameters, this function essentially agrees with our own, confirming our suspicions. do_action, albeit longer and a tad more complex, boils down to the following code:

function do_action($tag, $arg = '') {
    [Omitted code that records statistics, processes arguments, and deals with filters]

    do {
        foreach ( (array) current($wp_filter[$tag]) as $the_ )
            if ( !is_null($the_['function']) )
                call_user_func_array($the_['function'], array_slice($args, 0, (int) $the_['accepted_args']));

    } while ( next($wp_filter[$tag]) !== false );

    [Omitted code that cleans up]
}

Looping through the associated functions and calling each one is not strange to us all at; in fact, it's exactly what we hypothesized in our implementation. Consequently, beginning with our own thoughts about the code not only helped us better understand it, but also required critical thinking and problem solving that is essential to software development.


Examples

With our understanding of what, why, and how well in hand, it's time to move on to two examples. The first is a play on RSS; rather than providing notifications via syndication, why not use e-mail instead?

E-mail Notification System

E-mail

To implement our system, we'll need to look for when a post is published. But which hook should we use? The API action reference provides a list of hooks along with descriptions of the associated events that can help us identify exactly this. Indeed, the publish_post hook's description matches what we need, so let's add a function handler to it:

add_action( 'publish_post', 'notify_via_email' );

All that's left is to send an e-mail notification to someone in the notify_via_email function handler. Note that the API action reference specifies that the publish_post hook passes the post ID as an argument to our handling function. This will allow us to get information about the post via the get_post function, like so:

function notify_via_email( $post_id ) {
    $post = get_post( $post_id );
    $to = 'example@example.org';    

    $subject = 'Post Published on ' . get_bloginfo( 'name' );
    $message = $post->post_title . ' was published on ' . get_bloginfo( 'name' ) . ' as of ' . $post->post_date . '. You may view it at ' . get_permalink( $post_id ) . '.';

    wp_mail( $to, $subject, $message );
}

After retrieving the post, we employ its title, date, and permalink in our e-mail message. It is then sent via the wp_mail function, which requires a recipient, subject, and message as its parameters. With that, our simple e-mail notification system is complete. Note that it is not recommended to call wp_mail numerous times at once, as it is bound to present a significant delay in the page load time once the publish post button is pressed.

Google Analytics Plugin

Analytics

At first, it might seem that a theme is a more suitable medium for such code. Quite the contrary, through, as a site's theme is susceptible to revision and change. As a result, the analytics code could easily get lost in a transition from one theme to the next.

Albeit slightly contrived, our first application was a quick introduction to using hooks. This second example, however, will lend itself much more to real-world use. We'll be creating a simple Google Analytics plugin that automatically inserts tracking code into the footer of a page.

At first, it might seem that a theme is a more suitable medium for such code. Quite the contrary, through, as a site's theme is susceptible to revision and change. As a result, the analytics code could easily get lost in a transition from one theme to the next. Creating a plugin bypasses this disadvantage; because it will remain active regardless of which theme is employed, the analytics code will still be present and remove the need for theme maintenance.

But how do we plug code into the footer of a website? This, too, is done via hooks. If you've worked on WordPress themes before, you've likely called the wp_head and wp_footer functions in the head and footer of your website, respectively. Both of these functions are present to simply activate hooks so that plugins can easily insert code into these vital areas of the page. Consequently, we'll simply add an action to the wp_footer hook:

add_action( 'wp_footer', 'add_google_analytics_code' );

Our add_google_analytics_code function, as the name suggests, prints out the Google Analytics code:

<?php function add_google_analytics_code() { ?>
<script>
    var _gaq=[['_setAccount','UA-XXXXX-X'],['_trackPageview']];
    (function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];
    g.async=g.src='//www.google-analytics.com/ga.js';
    s.parentNode.insertBefore(g,s)}(document,'script'))
</script>
<?php } ?>

Remember to change UA-XXXXX-X to your site-specific ID and you're all set! Just add this code to a file and upload it to your WordPress plugins directory. Be sure to insert a plugin header, like so:

<?php
/*
Plugin Name: Wptuts+ Google Analytics Plugin
Plugin URI: [Insert a link to this article.]
Description: This plugin adds Google Analytics tracking code to the footer of a WordPress site.
Version: 1.0
Author: Your Name
Author URI: http://example.org
License: GPL2
*/
?>

Don't forget to change the author name, the author URI, and, of course, activate the plugin!


Conclusion

By splitting up hooks into a concept, an implementation, and examples, we have effectively examined them from inside the WordPress core to outside in a real-world application. Rather than merely looking at the WordPress core code, we implemented our own version of hooks by inference, allowing us to obtain a deeper, developer-like understanding of this powerful system. Finally, with a real-world Google Analytics Plugin application, we obtained preliminary insights into how useful hooks can really be. Be sure to join us next time and feel free to share your own, innovative uses of hooks in the comments below!

Advertisement