I inherited a WooCommerce project that was a total mess. We’re talking years of different developers, overlapping features, and a dozen custom plugins. The client’s complaint was simple: “Sometimes, when we update a product, the cache doesn’t clear. Other times, it clears twice.” Total nightmare. The culprit? A chaotic web of add_action and add_filter calls scattered across countless class constructors. To figure out what a single class was responsible for, you had to read the entire file. There was no clear contract, which is why we need to talk about WordPress PHP Interfaces.
Putting your hooks in a class constructor seems logical at first. The object gets created, the hooks get added. Done. But it creates tightly-coupled code that’s a pain to test and impossible to understand at a glance. My first thought was to just refactor the hooks into a dedicated init() method for each class. And yeah, it looked a bit cleaner. But it didn’t solve the real problem. I was just moving the mess around. The code still wasn’t predictable.
A Better Way: Using PHP Interfaces for Hooks
An interface is just a contract. It doesn’t contain any logic itself. It’s a set of rules that says, “Any class that implements me MUST have these specific public methods.” This forces a predictable structure onto your code. Instead of guessing where hooks are registered, you know exactly where to look. Trust me on this, it makes a huge difference.
Let’s define a simple contract for any class that needs to register WordPress actions. We’ll create an interface that requires a single method: get_actions().
interface ActionHookSubscriberInterface {
/**
* Returns an array of actions that the object needs to be subscribed to.
*
* @return array
*/
public static function get_actions();
}
Now, any class in our plugin can “sign” this contract. They agree to provide a static method named get_actions that returns an array of their hooks. Then we can build a simple manager to handle the registration for us. This general approach is something I first saw detailed over at carlalexander.ca, and it’s solid.
class HookManager {
public function register( $object ) {
if ( $object instanceof ActionHookSubscriberInterface ) {
foreach ( $object::get_actions() as $hook => $method ) {
add_action( $hook, [ $object, $method ] );
}
}
}
}
// And here's a class that uses it:
class My_Custom_Class implements ActionHookSubscriberInterface {
public static function get_actions() {
return [
'save_post' => 'clear_post_cache',
];
}
public function clear_post_cache( $post_id ) {
// ... do the actual work here
}
}
// Now, registration is clean.
$manager = new HookManager();
$my_class = new My_Custom_Class();
$manager->register( $my_class );
So, What’s the Real Payoff?
The magic here is decoupling and predictability. The HookManager doesn’t need to know anything about My_Custom_Class except that it fulfills the ActionHookSubscriberInterface contract. That’s it. Your classes are no longer responsible for registering themselves; they are only responsible for declaring the hooks they need.
- It’s Self-Documenting: You see
implements ActionHookSubscriberInterfaceand you instantly know the class registers action hooks. - It’s Testable: You can test your class methods without ever touching the WordPress event system.
- It’s Maintainable: When a new developer joins, they don’t have to hunt for hooks. They just look for the contract.
Look, this stuff gets complicated fast. If you’re tired of debugging someone else’s mess and just want your site to work, drop my team a line. We’ve probably seen it before.
Stop putting hooks in constructors. Start thinking in terms of contracts. It takes a bit more setup, but it will save you from a world of pain down the road. What’s the biggest code mess you’ve ever had to untangle?
Leave a Reply