Want to learn object-oriented programmming? Get my free course

Helping WordPress make friends with the decorator pattern

In some plugin circles (also know as the “cool kids club”), the coolest kid on the block is the wpdb class. Plugins go out of their way to be his friend. Lucky guy (or class)!

One plugin that has to be friends with wpdb is HyperDB. If plugins could talk, it would sound a lot like a scene from kindergarden.

HyperDB: Hey WPDB, wanna be friends? I’ll make you the strongest database class there is!
WPDB: Woah, that sounds awesome. I’m in!
HyperDB: There’s only one catch. You can’t be friends with anyone else.
WPDB: Oh ok. *sad face*

HyperDB uses the proxy pattern to give wpdb his new powers. The HyperDB class achieves that by extending the wpdb class. This is also a limitation of the proxy pattern.

A class can only extend one class. This means that it can only have one proxy (in practice). Creating a proxy prevents others from creating one. This leaves other plugins like Query Monitor in the wind.

Query Monitor: Hey WPDB, how would you like to know when you’re sick?
WPDB: I’d love to! I hate being sick.
Query Monitor: Great! All you need to do is become my friend and I’ll help you with that.
WPDB: Sorry Query Monitor, I’m already friends with HyperDB…

This situation is unfortunate for wpdb. HyperDB could use the diagnostic powers that Query Monitor provides. The proxy pattern won’t have it though. It limits these relationships to one or the other.

How can wpdb have as many friends as he wants?

The smart proxy

One of the nicknames of the decorator pattern is “smart proxy”. That’s because the decorator pattern is like the proxy pattern. Both have the goal to add new functionality to an existing class. The difference is that it does so in a “smart” way without extending the class.

This means that you can decorate a class as many times as you want. wpdb can now have more than one friend (yay). Let’s see how that works.

It starts with an interface

In contrast with the proxy pattern, the decorator pattern is about sharing the same interface. This is an important distinction. It’s the core element that distinguishes the decorator from the proxy.

/**
 * Interface used by classes representing post titles.
 */
interface PostTitleInterface
{
    /**
     * Get the post title.
     *
     * @return string
     */
    public function get_title();
}

/**
 * A simple post title.
 */
class PostTitle implements PostTitleInterface
{
    /**
     * The post title.
     *
     * @var string
     */
    private $title;

    /**
     * Constructor.
     *
     * @param string $title
     */
    public function __construct($title = '')
    {
        $this->title = $title;
    }

    /**
     * Get the post title.
     *
     * @return string
     */
    public function get_title()
    {
        return $this->title;
    }
}

The PostTitleInterface interface defines the contract for post titles. The PostTitle is a simple implementation of that contract. It stores the title string and returns it when you call the get_title method.

The PostTitleInterface allows you to break away from the rigidity of the proxy pattern. A decorator doesn’t need to extend his delegate. He just needs to implement the same interface as his delegate.

Decorating your post title

So let’s say you want to sanitize your post title. A post title proxy that did that would look like the example below.

/**
 * A sanitized post title.
 */
class SanitizedPostTitle extends PostTitle
{
    /**
     * Get the sanitized post title.
     *
     * @return string
     */
    public function get_title()
    {
        return sanitize_title(parent::get_title());
    }
}

The SanitizedPostTitle class extends the PostTitle class. This allows it to alter the behaviour of the get_title method. This allows it to sanitize the title.

You run into the same limitation as wpdb though. You can’t know if someone else already extended PostTitle. This limits your ability to change the post title. The decorator bypasses this problem with the use of the PostTitleInterface.

/**
 * A post title decorator that sanitizes post titles.
 */
class SanitizePostTitleDecorator implements PostTitleInterface
{
    /**
     * A post title.
     *
     * @var PostTitleInterface
     */
    private $post_title;

    /**
     * Constructor.
     *
     * @param PostTitleInterface $post_title
     */
    public function __construct(PostTitleInterface $post_title)
    {
        $this->post_title = $post_title;
    }

    /**
     * Get the sanitized post title.
     *
     * @return string
     */
    public function get_title()
    {
        return sanitize_title($this->post_title->get_title());
    }
}

The SanitizePostTitleDecorator class is quite different from the SanitizedPostTitle class. You’ll notice that it doesn’t extend PostTitle instead it just implements the PostTitleInterface. The constructor is different as well. You don’t take in the post title string, but an instance of “PostTitleInterface”.

This goes back to the idea that an interface is about a contract. The SanitizePostTitleDecorator class only cares about the get_title method from the PostTitleInterface. That’s the method it wants to alter the output of.

It doesn’t matter if you’re getting an instance of PostTitle, SanitizedPostTitle or something else. As long as it implements the PostTitleInterface, it’ll work. Simple as that. This gives you a lot more freedom when designing your class and how they interact.

Nesting decorators

This flexibility allows you to nest decorators. This allows you to decorate a post title as many times as you want. This is a powerful property of the decorator. Let’s create a second decorator so that we can nest it with our SanitizePostTitleDecorator.

/**
 * A post title decorator that changes post titles to upper case.
 */
class UpperCasedPostTitleDecorator implements PostTitleInterface
{
    /**
     * A post title.
     *
     * @var PostTitleInterface
     */
    private $post_title;

    /**
     * Constructor.
     *
     * @param PostTitleInterface $post_title
     */
    public function __construct(PostTitleInterface $post_title)
    {
        $this->post_title = $post_title;
    }

    /**
     * Get the upper cased post title.
     *
     * @return string
     */
    public function get_title()
    {
        return strtoupper($this->post_title->get_title());
    }
}

The UpperCasedPostTitleDecorator class is almost the same as the SanitizePostTitleDecorator class. There’s only one small difference. It modifies the title using the strtoupper function instead of the sanitize_title function.

Now let’s say you want a post title that is both upper cased and sanitized. You could achieve that by nesting these two decorators. Let’s look at an example of that.

$post_title = new PostTitle('A post title');
$post_title = new SanitizePostTitleDecorator($post_title);
$post_title = new UpperCasedPostTitleDecorator($post_title);

// Output: A-POST-TITLE
$title = $post_title->get_title();

It all starts with a PostTitle object. You pass that object to the constructor of the SanitizePostTitleDecorator class. You then pass that object to the UpperCasedPostTitleDecorator. This is possible because every class implements the PostTitleInterface.

The magic happens when you call the get_title method. It’ll go all the way up to the get_title method in the PostTitle object which returns its title value. The SanitizePostTitleDecorator object sanitizes it. Following that, the UpperCasedPostTitleDecorator object makes it upper cased. You now have the sanitized and upper cased value of the title in your PostTitle object.

Going back to wpdb

This concept of nesting is important to the issues discussed at the beginning of the article. If it was possible to decorate wpdb, these issues wouldn’t exist in the first place. You could nest HyperDB and Query Monitor together.

Could we bring a bit of the magic of the decorator pattern to wpdb? Why yes, we could! The result is a wrapper class that behaves in a similar way to a decorator.

Overcoming the lack of interface

There’s one large issue with implementing the decorator pattern with wpdb. It’s the fact that it doesn’t have an interface. This means that there’s no contract for us to follow. There’s nothing to bind the decorators together either.

This isn’t a fatal problem. You can overcome it by using a lot of PHP magic methods.

/**
 * A blank WPDB decorator.
 */
class WPDBDecorator
{
    /**
     * The decorated instance of WPDB.
     *
     * @var mixed
     */
    private $wpdb;

    /**
     * Constructor.
     *
     * @param mixed $wpdb
     */
    public function __construct($wpdb)
    {
        $this->wpdb = $wpdb;
    }

    /**
     * Intercepts all calls to the WPDB object.
     *
     * @param string $method
     * @param array  $arguments
     *
     * @return mixed
     */
    public function __call($method, array $arguments)
    {
        return call_user_func_array(array($this->wpdb, $method), $arguments);
    }

    /**
     * Get the given WPDB variable.
     *
     * @param string $variable
     *
     * @return mixed
     */
    public function __get($variable)
    {
        return $this->wpdb->$variable;
    }

    /**
     * Sets the value of the given WPDB variable.
     *
     * @param string $variable
     * @param mixed  $value
     */
    public function __set($variable, $value)
    {
        $this->wpdb->$variable = $value;
    }

    /**
     * Checks if the given WPDB variable is set.
     *
     * @param string $variable
     *
     * @return bool
     */
    public function __isset($variable)
    {
        return isset($this->wpdb->$variable);
    }

    /**
     * Unsets the given WPDB variable.
     *
     * @param string $variable
     */
    public function __unset($variable)
    {
        unset($this->wpdb->$variable);
    }
}

This is a blank wpdb decorator class with PHP magic methods. You can use it as a starting template for your own wpdb decorator. Let’s see how it works before adding some logic to it.

You’ll notice that each magic method performs the same operation on the wpdb object stored internally. We need our decorator to behave the same as wpdb would. That’s because there’s no interface to tell us how wpdb should behave.

The constructor is different as well. There’s no type checking. Our pseudo-decorator doesn’t have an interface. Neither would another nested decorator like Query Monitor. We have to assume that someone will pass a compatible object to the constructor.

Adding logic to the decorator

Let’s add some logic to our decorator now. We’re going to aim for the same outcome as the proxy pattern example. We’re going to create a decorator to track wpdb errors.

The task of the decorator is the same as the proxy. You need to check the last_error variable for an error. The difference is how it’s done.

/**
 * Decorator for WPDB that checks for errors.
 */
class WPDBErrorCheckingDecorator
{
    /**
     * The decorated instance of WPDB.
     *
     * @var mixed
     */
    private $wpdb;

    /**
     * Constructor.
     *
     * @param mixed $wpdb
     */
    public function __construct($wpdb)
    {
        $this->wpdb = $wpdb;
    }

    /**
     * Intercepts all calls to the WPDB object. If the method is tracked, performs analysis on it.
     *
     * @param string $method
     * @param array  $arguments
     *
     * @return mixed
     */
    public function __call($method, array $arguments)
    {
        if (!in_array($method, array('delete', 'get_col', 'get_results', 'get_row', 'get_var', 'query', 'update'))) {
            return call_user_func_array(array($this->wpdb, $method), $arguments);
        }

        $return = call_user_func_array(array($this->wpdb, $method), $arguments);

        if ('' !== $this->wpdb->last_error) {
            do_action('wpdb_error', $this->wpdb->last_error, $this->wpdb->last_query);
        }

        return $return;
    }

    /**
     * Get the given WPDB variable.
     *
     * @param string $variable
     *
     * @return mixed
     */
    public function __get($variable)
    {
        return $this->wpdb->$variable;
    }

    /**
     * Sets the value of the given WPDB variable.
     *
     * @param string $variable
     * @param mixed  $value
     */
    public function __set($variable, $value)
    {
        $this->wpdb->$variable = $value;
    }

    /**
     * Checks if the given WPDB variable is set.
     *
     * @param string $variable
     *
     * @return bool
     */
    public function __isset($variable)
    {
        return isset($this->wpdb->$variable);
    }

    /**
     * Unsets the given WPDB variable.
     *
     * @param string $variable
     */
    public function __unset($variable)
    {
        unset($this->wpdb->$variable);
    }
}

We’re not extending the wpdb class like the proxy pattern would. The insides of the wpdb class aren’t available to us. This means that we can’t use the query method for tracking errors.

The problem is that the wpdb class calls the query method internally. We can’t capture these calls even if we create a query method in your decorator. We have to track every wpdb method that uses the query method.

public function __call($method, array $arguments)
{
    if (!in_array($method, array('delete', 'get_col', 'get_results', 'get_row', 'get_var', 'query', 'update'))) {
        return call_user_func_array(array($this->wpdb, $method), $arguments);
    }

    $return = call_user_func_array(array($this->wpdb, $method), $arguments);

    if ('' !== $this->wpdb->last_error) {
        do_action('wpdb_error', $this->wpdb->last_error, $this->wpdb->last_query);
    }

    return $return;
}

We’re using the __call magic method to do that. The __call magic method receives the method name as a parameter. We can check that name against a list of wpdb methods that use the query method internally. That way we know when to check for errors.

Installing the decorator

Installing your decorator is easier than a proxy. You don’t need to use db.php. You can replace the global wpdb object when WordPress loads your plugin.

global $wpdb;

$wpdb = new WPDBErrorCheckingDecorator($wpdb);

A happy ending for wpdb

The decorator pattern is great for wpdb. He can now have a lot more friends. They let him achieve more than he could ever before. Does that mean that the proxy pattern is a bad friend to wpdb? Nope!

It depends on the nature and size of the changes. HyperDB changes the way wpdb works in a fundamental way. It might be possible to do it using a decorator (or several ones). That said, it makes a lot more sense to use the proxy pattern.

At the other end of the spectrum, caching and error tracking are smaller problems to solve. They’re more suited to the decorator pattern. These types of decisions and tradeoffs are what object-oriented design is about.

Creative Commons License