Had a client come in with a plugin that was a complete nightmare. It was a critical piece of their inventory management system, but every time their team tried to add a feature, something else would shatter. The code was a tangled mess of classes calling each other directly, with `add_action` and `add_filter` calls buried deep inside constructors and methods. Total spaghetti. They were losing money and hair. Fast.
My first move was to try and isolate the biggest problem child, a class called `Product_Sync`. I figured I’d refactor it, clean it up, and get a quick win. And yeah, that didn’t work. Turns out, `Product_Sync` was directly calling methods in the `Order_Manager`, the `User_Profile` class, and two other custom classes. It was a domino effect from hell. This is the exact reason why the WordPress mediator pattern isn’t just some academic concept—it’s a lifesaver.
Understanding the WordPress Mediator Pattern
Think about it like an airport. You don’t want every pilot talking directly to every other pilot to figure out who lands when. It would be chaos. Instead, every plane talks to the control tower. The tower is the single source of truth, the mediator that coordinates everything. No plane talks to another plane directly.
Here’s the kicker: WordPress already gave you a control tower. It’s the Plugin API. The whole system of hooks and filters is a massive, built-in mediator. When you cram `add_action()` calls inside a random class method, you’re letting a 747 talk directly to a Cessna. You’re ignoring the system that was built to prevent chaos. The goal is to embrace the mediator, not fight it. The original idea, which I saw explained well over at carlalexander.ca, is to treat the Plugin API with the respect it deserves.
So How Do You Actually Do It?
You stop putting hooks inside your logic classes. Period. Instead, you create a dedicated class—a subscriber or a manager—whose only job is to talk to the control tower. Your core logic classes should be pure, clean, and completely unaware that WordPress hooks even exist. They just do their job.
<?php
/**
* This class ONLY talks to WordPress hooks. Nothing else.
* This is our control tower operator.
*/
class Hook_Subscriber {
protected $inventory_manager;
public function __construct( Inventory_Manager $inventory_manager ) {
$this->inventory_manager = $inventory_manager;
}
public function register_hooks() {
add_action( 'woocommerce_order_status_completed', array( $this->inventory_manager, 'reduce_stock' ), 10, 1 );
}
}
/**
* This class ONLY handles inventory logic.
* It has no idea what an "action" or "filter" is.
*/
class Inventory_Manager {
public function reduce_stock( $order_id ) {
// ... all your clean, testable logic lives here.
// Get the order, get the products, reduce stock counts.
// No WordPress coupling. Pure logic.
}
}
// Then, in your main plugin file, you wire them up:
$inventory_logic = new Inventory_Manager();
$hook_subscriber = new Hook_Subscriber( $inventory_logic );
$hook_subscriber->register_hooks();
Why Does This Matter?
Doing it this way gives you a clean separation of concerns. Your `Inventory_Manager` is now 100% testable on its own, without having to load a full WordPress environment. The `Hook_Subscriber` is dumb; it just registers events. You’ve decoupled your core business logic from the WordPress API. Trust me on this, your future self will thank you when a client asks for a new feature and you don’t have to spend a week untangling spaghetti code just to get started.
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.
Leave a Reply