In a not so distant future, you’ve grown to be quite the WordPress expert. You work for WSIS (WordPress Security Intelligence Service) as an analyst. You’re given your first field mission.
You have to get in deep with WordPress. You need to get intimate information about a WordPress object. Information even WordPress doesn’t want to give you. You need to gain access without detection. The last thing you need is WordPress to know you’re listening to things.
How would your future self do it? Well, he’d use the proxy pattern (good thing you’re reading this article). The proxy pattern is the equivalent of object-oriented tapping. It’s one way to solve the problem of interacting with a class without it being aware of it.
Let’s look at how it works.
Object-oriented tapping with the proxy pattern
The proxy pattern is about the relationship between two classes. Those two classes are the proxy and the delegate.
The delegate is the class the proxy wants to interact with silently. What the delegate does isn’t important (sorry delegate class). The proxy class is where all the action happens.
The goal of the proxy class is flexible. What defines a proxy class is the idea exerting control on the delegate without it being aware of it.
How does the proxy control the delegate?
When you’re using the proxy pattern, the proxy class extends its delegate. This is important. That’s how it can exert so much control without it knowing. As a child class, it can use all the tools that inheritance puts at its disposal.
/** * A delegate class with an internal data array. */ class Delegate { /** * Some internal data. * * @var array */ protected $data; /** * Constructor. * * @param array $data */ public function __construct(array $data = array()) { $this->data = $data; } /** * Get the internal data of our delegate. * * @return array */ public function get_data() { return $this->data; } /** * Sets the internal data of our delegate. * * @param array $data */ public function set_data(array $data) { $this->data = $data; } } /** * Proxy class that turns our delegate into a read-only class. */ class Proxy extends Delegate { public function set_data(array $data) { // I'm sorry. It seems we misplaced your data. } }
Above is a small example to show you how a Proxy
can alter the behaviour of a Delegate
class. With just a bit of code, the Proxy
class became a read-only version of the Delegate
class. All that the Proxy
class needed to do was override the set_data
method and leave it blank. And the Proxy
class did it all without the Delegate
class ever knowing.
Protecting your delegate
It might sound like the proxy pattern is evil. That’s not the case. It’s quite powerful, but someone can abuse it. When you’re designing a class, it’s important to keep in mind that someone else (or yourself) might decide to tamper with it using a proxy class.
There’s only one way to protect your delegate class from tampering. You have to make sure that it uses proper visibility to control access. The only visibility level that will prevent tampering is private
.
In the case of WordPress, it tends to leave everything public so that makes it easy for you to create a proxy class (insert mad scientist laugher). We’ll see an example of that now.
Tapping into the wpdb object
The wpdb
class is an interesting class to proxy. It’s one of the main points of focus when you’re interested in performance and caching ( and who isn’t!?). There’s a lot of interesting ways to leverage it with a proxy class.
We’re not going to go too crazy for this example though. We’re going to add a bit of error tracking to the wpdb
class. To do that, we’ll create a proxy class that checks for database errors. The class then sends those errors to the plugin API which allows other plugins to use that information.
Notes on wpdb
The hardest part of this example is to find where to listen with our proxy class. The wpdb
class is quite large and complex. There’s a lot of methods and internal variables.
Analyzing the entire class is beyond the scope of this article. That said, let’s go over the class details that are important for this example. This will help you understand the proxy class that you’ll see later.
The query
method is central to the wpdb
class . Every interaction with the database uses it. It performs the given query and returns the result. If there was an error, it returns false.
The class keeps track of errors internally using the last_error
variable. The variable always starts off as an empty string for each query. If there’s an error, the class stores the error message there.
The class also tracks queries with the last_query
variable. WordPress has a filter to alter queries before it sends them to the database. The final query that’s sent is always the one stored in that variable.
Creating our wpdb proxy class
Let’s start with an empty wpdb_proxy
class. Based on our notes from earlier, you want to override the query
method. This will allow it to listen to every database interaction that WordPress does. You’ll be able to detect every database error as they happen.
/** * A wpdb proxy that checks for database errors and sends them to the plugin API. */ class wpdb_proxy extends wpdb { /** * Perform a database query using current database connection. * * @param string $query Database query * * @return int|false */ public function query($query) { $return = parent::query($query); // Do stuff here return $return; } }
The next step is to detect those errors. You can do that by checking the last_error
class variable. You want to check if the variable is an empty string. If it isn’t, that means there was an error with the query that the class just performed.
/** * A wpdb proxy that checks for database errors and sends them to the plugin API. */ class wpdb_proxy extends wpdb { /** * Perform a database query using current database connection. * * @param string $query Database query * * @return int|false */ public function query($query) { $return = parent::query($query); if ('' !== $this->last_error) { do_action('wpdb_error', $this->last_error, $this->last_query); } return $return; } }
The class then passes the error to the new wpdb_error
action. It’s also useful to send the query with the error so that’s passed as the second argument. You’ll notice that the class uses the last_query
class variable in the action hook. It doesn’t use the query
method variable. That’s because we want to pass the query that WordPress sent to the database and not the one passed to the method.
Installing your proxy class
Installing a new wpdb
class is a bit of a challenge. You need to create a db.php
file with your proxy class. WordPress will load the file if you put it in the wp-content
folder.
<?php // wp-content/db.php if (!defined('ABSPATH')) { die(); } /** * A wpdb proxy that checks for database errors and sends them to the plugin API. */ class wpdb_proxy extends wpdb { /** * Perform a database query using current database connection. * * @param string $query Database query * * @return int|false */ public function query($query) { $return = parent::query($query); if ('' !== $this->last_error) { do_action('wpdb_error', $this->last_error, $this->last_query); } return $return; } } $wpdb = new wpdb_proxy(DB_USER, DB_PASSWORD, DB_NAME, DB_HOST);
You can find the logic for loading a custom database object in the require_wp_db
function.
A powerful tool in your arsenal
The proxy pattern allows you to alter a delegate class in significant ways. The examples shown in this article are but a fraction of what you can do with it. It’s one of the most important patterns that you can learn.
Its variety of uses is a testament to the power of inheritance in object-oriented programming.