Maintainable Ways to Set Up WordPress Hooks

What Are Hooks?

In WordPress, we’re encouraged to use their hooks and filters. So what are they? Basically, they allow one to plug custom functionality into WordPress at predefined points of execution. I’m sure you’ve seen them; ever dealt with add_action("init", 'setup_theme_plugins'); ? That’s a way of telling WordPress, “When you get to the ‘init’ point, run ‘setup_theme_plugins’.” Simple enough, right?

Yes and no; for simple actions, a little function might be appropriate, but what about for more complex actions, such as when you need to add a settings page to the admin area? You quickly can create messy code, throwing functions around all willy-nilly. A simple “admin_menu” action could easily end up with a function to add the page, one to render the html, one to process POST requests since it is a form, one to grab values, etc. That’s already 4 functions that are related but you maybe don’t know where you should put them all. Should they go in functions.php? No? Maybe in admin_menu.php and then require it? What about actions.php and require that? It just doesn’t quite feel right.

So What Shall We Do?

We know from the WordPress Codex that we are supposed to use a callable for the second argument to add_action. So since we have all these related functions, maybe we can put them in a class and call one of the methods in the class, right? Well, we could; here’s what that would look like:

$page = new SettingsPage();
add_action("admin_menu", [$page, "setupAction"]);

So, yeah, that does kind of look better and kind of look worse. Meh; we now have a proper place to put all the related functions for maintaining this settings page, but that array syntax for specifying the callable is really ugly, isn’t it? :/ If only there was some way to tell WordPress that the Class itself defines what to do with the action…

The Magic of PHP’s “__” Functions

So ever heard of “Operator Overloading”? Well, PHP decided to throw the official definition to the wind and come up with it’s own definition that will confuse anyone not from PHP. So, from now on, let’s call PHP’s concept of “Operator Overloading” something else: “Magic Methods”. These Magic Methods are aptly named as you don’t call them directly; PHP’s internal engine will call these magic methods when specific conditions occur, such as calling a non-existent method, or serializing an object. Let’s take a look at an example:

class Foo {
    public function bar() {
          echo "in bar!";
    }

    public function __call($name, array $args) {
        echo "tried calling {$name}, but that method doesn't exist!";
    }
}

If we try to call $foo->baz(), PHP will intercept that method call and dispatch the __call magic method, because baz does not exist in the class definition! You should see “tried calling baz, but that method doesn’t exist!” echo to your SAPI’s standard output stream.

Here’s a full list of these magic methods: PHP Magic Method Documentation.

How Does This Help Us Clean up Action Hooks?

If you read through that last link, you should have ran across one magic method called “__invoke”. This magic method is code to be ran when an object is used like it’s a function… or a callable. For example, the following will cause the __invoke magic method to be run.

$class = new MyAwesomeClass();
echo $class(); // notice the parenthesis on the end of the object!

We’re using $class in a callable manner here, which is what WordPress’ actions and hooks want: a callable. Are the lights going on yet? Instead of using the array notation, we can put the action hooks execution code into the __invoke method, so it’s called automatically when we register an action like add_action("admin_menu", new MenuAction); ! Isn’t that cool? Super readable, we have a place to put the related code, and don’t have to sacrifice anything! It’s completely a win-win situation.

Here’s a working repository that uses this idea:

Call to add_action()

Implementation of MenuAction Class