I picked up a ticket for a high-traffic WooCommerce site where the client was stuck. They had one plugin for database scaling, a HyperDB-style read/write replica setup, and another for deep performance analysis like Query Monitor. The two would not run at the same time. The site could be fast or it could be debuggable, but never both.
This is a classic WordPress issue, where two plugins try to modify the same core object, in this case, the global $wpdb. Both plugins wanted to extend the wpdb class to add their functionality. When two plugins try to be the “one true” extension of a class, only one can win. The other one just breaks.
My first thought was this might be a load order problem. You know, just a simple hack in a mu-plugin to make sure one loads after the other, letting them chain together. And yeah, that worked… for about five minutes. It turns out extending a class is a brute-force approach. The second plugin had no way of knowing what the first one did, so it just stomped all over it. The real fix had to be at the architectural level, using something more flexible like the WordPress decorator pattern.
Understanding the decorator pattern for WordPress
Instead of extending a class (inheritance), the decorator pattern is about wrapping it (composition). You create a new class that holds the original object and implements the same interface. That lets you add behavior before or after you call the original object’s methods. Because your wrapper implements the same interface, another decorator can wrap it, and then another. You can stack them like LEGOs.
The big hurdle with $wpdb is that it has no interface, so there is no contract to follow. We can work around that with a bit of PHP magic. The approach handles this classic problem well, and it builds on an idea I saw over at carlalexander.ca.
Here is a basic decorator that logs database errors without replacing $wpdb entirely.
/**
* Decorator for WPDB that checks for errors.
*/
class WPDBErrorCheckingDecorator
{
/**
* The decorated instance of WPDB.
*
* @var wpdb
*/
private $wpdb;
/**
* Constructor.
*
* @param wpdb $wpdb
*/
public function __construct($wpdb)
{
$this->wpdb = $wpdb;
}
/**
* Intercepts all calls to the WPDB object.
*/
public function __call($method, array $arguments)
{
// Only run our logic for methods that execute queries
$tracked_methods = ['delete', 'get_col', 'get_results', 'get_row', 'get_var', 'query', 'update'];
if ( ! in_array($method, $tracked_methods) ) {
return call_user_func_array([$this->wpdb, $method], $arguments);
}
// Run the original method
$return = call_user_func_array([$this->wpdb, $method], $arguments);
// Now, check for an error
if ( ! empty($this->wpdb->last_error) ) {
do_action('wpdb_error', $this->wpdb->last_error, $this->wpdb->last_query);
}
return $return;
}
/**
* Pass property access through to the original object.
*/
public function __get($variable)
{
return $this->wpdb->$variable;
}
/**
* Pass property setting through to the original object.
*/
public function __set($variable, $value)
{
$this->wpdb->$variable = $value;
}
}
// In your plugin's main file:
global $wpdb;
$wpdb = new WPDBErrorCheckingDecorator($wpdb);What’s the takeaway?
The magic methods (__call, __get, __set) let the decorator act as a pass-through for any method or property we do not want to change. We catch the methods that run queries, run them, then add the error checking afterward, so nothing conflicts. Another plugin could wrap our WPDBErrorCheckingDecorator instance to add caching, and it would still work.
- Proxy pattern (extending): easy to implement but rigid, and it creates conflicts because only one class can extend another.
- Decorator pattern (wrapping): more flexible, and it lets you stack several features on top of each other. This is the cleaner way to solve the problem.
For big, fundamental changes like HyperDB, extending the class might still be the pragmatic choice. But for smaller, self-contained features like logging or caching, the decorator pattern is almost always the right call. It stays out of the way of other plugins and works with the rest of the ecosystem.
This stuff gets complicated fast. If you are tired of debugging someone else’s mess and just want your site to work, drop my team a line. We have probably seen it before.