# Designing a class to build simple WordPress queries

In a [previous article](https://carlalexander.ca/designing-class-manage-wordpress-posts), we looked at how you could create a class to manage WordPress posts. We ended creating a class who's job it was to interact with the WordPress database. And it did that quite well! As part of its job, our class also had to be able to query the WordPress database. To do that, we made it easier to reuse code around the [`WP_Query`](https://carlalexander.ca/wordpress-adventurous-wp-query-class/) class. We achieved that by creating methods that used predefined query arguments. But have you ever looked at the [codex page](https://codex.wordpress.org/Class_Reference/WP_Query) for `WP_Query`? Holy Moley, there are a lot of query parameters in there! It's pretty intimidating and not always easy to use in practice. That's the problem that we're going to look at in this article. We'll design a class to simplify how we build `WP_Query` objects. It'll handle all the complexity around `WP_Query` query parameters for you. The result should be an easier way for you for you to create WordPress queries.

# How do you make WordPress queries easier?

Before we proceed any further, we should take a moment to answer this question. Now, we know that the job of the `WP_Query` class is to do just that. It lets you query the database without the need for you to know any SQL. You just need to fill an array with query arguments and pass it to the `WP_Query` object. It acts as a [database abstraction layer](https://en.wikipedia.org/wiki/Database_abstraction_layer). This is good because not every WordPress developer knows SQL. And it's a lot easier to remember a bunch of query arguments than to learn it. Instead, WordPress just uses those query arguments and crafts the MySQL query for you. This also eliminates potential [security holes](https://en.wikipedia.org/wiki/SQL_injection). If you don't know SQL that well, you can introduce security holes by mistake. But, if you let WordPress take care of it, you don't have to worry about it. (Nice!) That said, the problem with query arguments is that they don't scale well. Your array of query arguments can grow quite large with a dozen or more arguments. It becomes hard to read and know what you're asking your query to do. # Improving WP\_Query readability

That's where our new class comes in. Its primary job will be to improve readability around `WP_Query`. And the way that we'll do it is by creating a [fluent interface](https://en.wikipedia.org/wiki/Fluent_interface) for building `WP_Query` objects. ## What is a fluent interface?

A fluent interface is a type object-oriented API. It's not an interface in the sense of an [object-oriented interface](https://carlalexander.ca/polymorphism-wordpress-interfaces/). (Yes, it's a bit confusing, but it's because API stands for [application programming interface](https://en.wikipedia.org/wiki/Application_programming_interface).) [Martin Fowler](https://en.wikipedia.org/wiki/Martin_Fowler) and Eric Evans [coined the expression for it in 2005](http://martinfowler.com/bliki/FluentInterface.html). The job of a fluent interface is to make your code more readable. It achieves that by using specific concepts and techniques that we'll look at a bit later. First, we'll go through a small example so that you have a better idea of what a fluent interface looks like. ### WP\_Query vs WP\_Query\_Builder

```
// WP_Query example
$query = new WP_Query(array(
    'fields' => 'ids',
    'post_type' => 'page',
    'orderby' => 'ID',
    'posts_per_page' => 5,
));
$posts = $query->get_posts();

// WP_Query_Builder example
$query_builder = new WP_Query_Builder();
$posts = $query_builder->select('ids')
                       ->from('page')
                       ->order_by('ID')
                       ->limit(5)
                       ->get_results();
```

Both queries shown above are trying to get the IDs of the 5 most recent pages ordered by ID. The first one is our standard `WP_Query` object with an array of query arguments. The other is the same query generated by our `WP_Query_Builder` class which uses a fluent interface. As you can see, both queries don't read the same at all even if they generate the same result. It's also possible that you find the first one easier to read. And that's ok! We'll look at why that is next. ## The elements of a fluent interface

As an object-oriented API, the fluent interface doesn't quite fit the mold of a [software design pattern](https://carlalexander.ca/moving-beyond-basics-software-design-patterns/). It's not quite a solution to a programming problem. That said, it does have some defining characteristics. ### Method Cascading

There's a good chance that you noticed the excessive use of method calls in the previous example. This is the most identifiable characteristic of a fluent interface. We call it [method cascading](https://en.wikipedia.org/wiki/Method_cascading). Method cascading is a more specific type of [method chaining](https://en.wikipedia.org/wiki/Method_chaining). Method chaining is a [type of syntax](https://en.wikipedia.org/wiki/Syntactic_sugar) where you ... chain method calls together. (Of course! Duh!) A good example of the use of method chaining is [jQuery](https://en.wikipedia.org/wiki/JQuery) which depends on it a lot. The goal of method chaining is to remove the need to store intermediate values. Each method returns an object which you can then call another method from. You can keep doing this as long as you want. Now, by default, method chaining has a serious problem. It's that each method can return a different object. If we look back at our previous example, you might assume that you're always dealing with the same `WP_Query_Builder` object. But method chaining doesn't guarantee that at all. (This violates the [law of Demeter](https://en.wikipedia.org/wiki/Law_of_Demeter), but we won't go into it today.) This is the problem that method cascading addresses. When you use method cascading, your methods always return the [current object](https://en.wikipedia.org/wiki/This_(computer_programming)). This means that you never have to worry about the type of object that a method is returning. You're always dealing with the same object that you started the method chain with. In this case, a `WP_Query_Builder` object. ### Domain-specific language

The other defining characteristic of a fluent interface is that it always uses a [domain-specific language](https://en.wikipedia.org/wiki/Domain-specific_language). A domain-specific language (or DSL) is type of [computer language](https://en.wikipedia.org/wiki/Computer_language). The goal of a domain-specific language is to give you the tools to solve a problem in a specific [domain](https://en.wikipedia.org/wiki/Domain_(software_engineering)). Meanwhile, the underlying programming language figures out how to do it. Domain-specific languages are often the best at what they do that. You use them every day without knowing that you're using one. For example, HTML, CSS and SQL are all domain-specific languages. Domain-specific languages are also better at articulating and describing the problem that they solve. This tends to make them easier to read than regular programming languages. It's also why Martin Fowler and Eric Evans chose the term fluent to describe the interface. It acknowledges the importance of fluency when designing a fluent interface. But this is also the hardest part of designing of a fluent interface. That's because [naming things is hard](https://carlalexander.ca/importance-naming-programming/). But it's even harder here where the language is an integral part of the solution. If you create or use a language that's confusing, you end up making things harder and not easier. #### Reusing the SQL domain-specific language

This is why we're not going to create a domain-specific language for our `WP_Query_Builder` class. Instead, we're going to reuse a well-known domain-specific language: [SQL](https://en.wikipedia.org/wiki/Sql). (You might have even noticed it in the example earlier.) Why are we choosing to use SQL as our domain-specific language? Wasn't the goal of `WP_Query` to remove the need for us to know SQL? Why go back to it now? These are all good valid questions! So like we saw at the beginning, the primary goal of the fluent interface is to provide more readable code. And, when you're starting off with WordPress or programming, SQL might be hard to read. But it won't stay that way forever. That's because SQL is everywhere in the programming world. There'll come a point when you'll have to start using it. And over time, it'll become something that's easy to read. And this is who we're going to design our fluent API for. We're not going to design it for someone who doesn't know SQL that well. The `WP_Query` query parameters do a good job of helping them already. Instead, we'll focus on those who find that `limit` makes more sense than `posts_per_page`. They're the developers that the `WP_Query` query arguments don't serve as well. They'll get the most out of our new fluent API. # Building our query builder

```
class WP_Query_Builder
{
}
```

Now that we've covered the theory around the `WP_Query_Builder` class, let's start building it. We'll start with an empty class and fill it with our domain-specific language methods from our initial example. These were `select`, `from`, `order_by`, `limit` and `get_results`. ## Constructor

But before we do all that, we can't forget about our friend the `__construct` method! What do we know about our `WP_Query_Builder` class? What does it need to work? Well, we know that it's an abstraction on top of `WP_Query` and that it works using query arguments. That means that we need an internal variable to store these query arguments. So let's start with that! ```
class WP_Query_Builder
{
    /**
     * The query arguments collected by the query builder.
     *
     * @var array
     */
    private $query_arguments;

    /**
     * Constructor.
     */
    public function __construct()
    {
        $this->query_arguments = array()
    }
}
```

Here's our first pass at the `__constructor` method. We started by creating a `query_arguments` internal variable to store all our query arguments. We then initialize it inside the `__construct` method with an empty array. But we can still do a bit more here. We could set some good default query arguments. These will help us improve the default performance of the `WP_Query` queries generated by our `WP_Query_Builder` class. ```
class WP_Query_Builder
{
    /**
     * The query arguments collected by the query builder.
     *
     * @var array
     */
    private $query_arguments;

    /**
     * Constructor.
     */
    public function __construct()
    {
        $this->query_arguments = array(
            'no_found_rows' => true,
            'update_post_meta_cache' => false,
            'update_post_term_cache' => false,
        );
    }
}
```

We added three default query arguments: `no_found_rows`, `update_post_meta_cache` and `update_post_term_cache`. By default, a `WP_Query` object query will ask WordPress to perform five database queries. Each of these three query arguments removes one of those five database queries. But it's a bit shortsighted to assume that we'll never want to change these default query arguments. For example, you might need to create a query where you want to update one or both of those caches. So let's update our `__construct` method so that you're able to do that. ```
class WP_Query_Builder
{
    /**
     * The query arguments collected by the query builder.
     *
     * @var array
     */
    private $query_arguments;

    /**
     * Constructor.
     *
     * @param array $query_arguments
     */
    public function __construct(array $query_arguments = array())
    {
        $this->query_arguments = array_merge(array(
            'no_found_rows' => true,
            'update_post_meta_cache' => false,
            'update_post_term_cache' => false,
        ), $query_arguments);
    }
}
```

We started by adding the `query_arguments` parameter to the `__construct` method. It uses an empty array as its default value. We then changed how we initialized the `query_arguments` internal variable. Instead of just assigning it an array, we now call [`array_merge`](https://secure.php.net/manual/en/function.array-merge.php) and assign the resulting array to it. The order that we pass the arguments to `array_merge` is important. `array_merge` processes arrays from left to right. That means that we want our array with our defaults to be first. That way, the `query_arguments` array that we passed as an argument can overwrite those defaults. ## Cascading methods

With the constructor out of the way, we can focus on the rest of the methods in our `WP_Query_Builder` class. Those are our cascading methods. These cascading methods will serve as implementations of our domain-specific language. The cascading methods in this article are going to stay quite simple. By that, we mean that they won't have a lot of inherent complexity. All that they'll do is map arguments to query parameters. There won't be that much logic besides that. Because all our simple methods are so similar, we'll start by creating a template method for them. Each simple method will make small changes to this template method. Understanding them will come in handy when you'll want to create a fluent interface of your own. ### Template method for method cascading

So, like we just mentioned earlier, all our simple methods follow a similar pattern. Well, almost all of them. The only exception is `get_results` which we'll cover at the end. Using this pattern, we can create a template method that we'll use for our cascading methods. ```
class WP_Query_Builder
{
    // ...

    /**
     * A method that implements method cascading.
     *
     * @param string $argument
     *
     * @return self
     */
    public function cascading_method($argument)
    {
        if (empty($argument)) {
            return $this;
        }

        // Do something with $argument.

       $this->query_arguments['query_parameter'] = $argument;

        return $this;
    }
}
```

Above is our template of a cascading method. We named it `cascading_method`. It has a single string parameter called `argument`. (This is just for this example. Your method doesn't have to have a parameter and it can also have more than one.) The most important feature of a cascading method is `return $this;`. This is what tells our method to return the current object. And you must ensure that our method *always* returns the current object. Otherwise, method cascading won't work. With that in mind, our cascading method should also always start with a [guard clause](https://en.wikipedia.org/wiki/Guard_(computer_science)). That's the [`if`](https://secure.php.net/manual/en/control-structures.if.php) statement at the beginning of the method. For `cascading_method`, the guard clause only checks if `argument` is [empty](https://secure.php.net/manual/en/function.empty.php) or not. In practice, you might want your guard clause to do more than just check if `argument` is empty or not. We want the guard clause is to check for all possible invalid `argument` values. That way, if `argument` is invalid, we return the current object right away. This ensures that our method always returns the current object and never breaks the cascade. Once passed our guard clause, `cascading_method` might do something with `argument`. It will then assign it to a query parameter inside our `query_arguments` internal array. It then finishes by returning the current object. ### select method

```
class WP_Query_Builder
{
    // ...

    /**
     * Specify the columns that the query will retrieve.
     *
     * Overwrites previous specification criteria if called multiple times.
     *
     * @param string $select
     *
     * @return self
     */
    public function select($select)
    {
        if (empty($select) || !is_string($select)) {
            return $this;
        }

        $this->query_arguments['fields'] = $select;

        return $this;
    }
}
```

The first method that we'll look at is the `select` method. As you can see, it's almost the same as our template method. The only thing that we added was a [`is_string`](https://secure.php.net/manual/en/function.is-string.php) check to our guard clause for the `select` variable. The purpose of the `select` method is to let you specify which columns we want our generated `WP_Query` object to return. That said, it's not even close to being as powerful as its [SQL equivalent](https://en.wikipedia.org/wiki/Select_(SQL)). This due to limitations with the [`fields`](https://codex.wordpress.org/Class_Reference/WP_Query#Return_Fields_Parameter) parameter that it maps to. By default, it'll tell `WP_Query` to return an array of `WP_Post` objects containing everything. It's like if you used `*` in SQL. Otherwise, it has two other options: `ids` and `id=>parent`. `ids` returns an array with all the post IDs. `id=>parent` returns `stdClass` objects with the `ID` and `post_parent` database columns. But that's it. We can't specify which database columns we want `WP_Query` to return to us. This makes this method a bit underwhelming in practice. ### from method

The next method that we'll look at is the `from` method. It lets us control the post types that the query will return. It's a bit more complicated than the `select` method that we saw earlier. But that's because of the fancier guard clause. ```
class WP_Query_Builder
{
    // ...

   /**
     * Specify the post types that the query will retrieve. 
     * 
     * Can be a comma separated string or an array. Overwrites previous
     * specification criteria if called multiple times.
     *
     * @param string|array $from
     *
     * @return self
     */
    public function from($from)
    {
        if (is_string($from)) {
            $from = array_map('trim', explode(',', $from));
        } elseif (!is_array($from)) {
            return $this;
        }

        $this->query_arguments['post_type'] = $from;

        return $this;
    }
}
```

If you read the [PHPDoc](https://en.wikipedia.org/wiki/PHPDoc) of the `from` method (you read PHPDoc right!?), it mentions that you can pass it a comma delimited string. The problem is that the [`post_type`](https://codex.wordpress.org/Class_Reference/WP_Query#Type_Parameters) parameter that it maps to doesn't. It only accepts a string with a single post type or an array of post types. So why did we decide to do that then? Well, there's no specific reason. ([Carl!!!](https://carlalexander.ca/wp-content/uploads/2016/08/Carl.jpg)) It's just a nice-to-have feature and it lets us see a more complex guard clauses. And, speaking of guard clause, how does it deal with a comma delimited string? First, it needs to check if `from` is a string using `is_string`. If it's a string, we convert it into an array using the [`explode`](https://secure.php.net/manual/en/function.explode.php) function. We then pass that array through the [`array_map`](https://secure.php.net/manual/en/function.array-map.php) function. This is an advanced PHP array function that lets us apply changes to every element of an array using a [callback](https://secure.php.net/manual/en/language.types.callable.php). In the code above, the callback is the [`trim`](https://secure.php.net/manual/en/function.trim.php) function. The goal is to remove any whitespace around our array element so that we're only left with the post type. The second guard clause checks if `from` is an array using [`is_array`](https://secure.php.net/manual/en/function.is-array.php). If it's not an array, we return the current object. Once passed the guard clauses, we're sure that `from` is an array of post types. We can assign it to the `post_type` query parameter without worrying about it. ### order\_by method

After the `from` method, let's look at the `order_by` method. It maps to `orderby` and `order` which are the two [ordering query parameters](https://codex.wordpress.org/Class_Reference/WP_Query#Order_.26_Orderby_Parameters). These let you control the order of the query results. ```
class WP_Query_Builder
{
    // ...

    /**
     * Specify the order of the query results.
     *
     * Overwrites previous specification criteria if called multiple times.
     *
     * @param string|array $sort
     * @param string $order
     *
     * @return self
     */
    public function order_by($sort, $order = 'DESC')
    {
        if (empty($sort) || (!is_array($sort) && !is_string($sort))) {
            return $this;
        } elseif (!is_string($order) || !in_array(strtoupper($order), array('ASC', 'DESC'))) {
            $order = 'DESC';
        }

        $this->query_arguments['orderby'] = $sort;
        $this->query_arguments['order'] = $order;

        return $this;
    }
}
```

This method a bit different from the other ones because it has two parameters: `sort` and `order`. Why did we decide to group these two query parameters? It's because of their close relationship. `order` is only taken into consideration when `orderby` is a string. If you're using an array, `WP_Query` won't use it. Instead, it expects it to be part of the array passed to `orderby`. Because of the dual nature of `orderby`, the guard clause is a bit more complex. You want to start by checking if `sort` is empty or not. Once you know that it isn't empty, you want to check that if it's an array or a string. To check that, you need both `is_array` and `is_string` to be `false`. The second guard clause is for the `order` parameter. It checks if `order` is a string and if that string is either `ASC` or `DESC`. But, if those two checks fail, it doesn't return the current object. Instead, it changes the value of `order` back to its default value of `DESC`. This is different from the guard clauses that we've seen so far. This goes back to the link between the `orderby` and `order` query parameters. We always need a valid `sort` value for `orderby`. But `order` can be invalid if `sort` is valid. We just reset it back to its default value when that happens. ### limit method

The `limit` method is the last and simplest method that we'll see. It maps to the [`posts_per_page`](https://codex.wordpress.org/Class_Reference/WP_Query#Pagination_Parameters) query parameter. It controls how many results our query will return. ```
class WP_Query_Builder
{
    // ...

    /**
     * Specify the maximum number of results that the query will retrieve.
     *
     * Overwrites previous specification criteria if called multiple times.
     *
     * @param int $limit
     *
     * @return self
     */
    public function limit($limit)
    {
        if (!is_numeric($limit)) {
            return $this;
        }

        $this->query_arguments['posts_per_page'] = (int) $limit;

        return $this;
    }
}
```

The interesting thing that is worth mentioning is the use of [`is_numeric`](https://secure.php.net/manual/en/function.is-numeric.php) in the guard clause. It's the only PHP function that will validate a numeric string and not just a number type. That's why we use it to check if `limit` is either a number or a numeric string. But this creates an another problem. The issue is that `posts_per_page` wants an [integer](https://secure.php.net/manual/en/language.types.integer.php) and `is_numeric` cannot guarantee that. It'll return true for other number types. That's why we [cast](http://php.net/manual/en/language.types.type-juggling.php) `limit` as an integer before assigning it to the `posts_per_page` query parameter. ### get\_results method

So far, we've built a few cascading methods for `WP_Query_Builder`. They highlighted the use of our template cascading method and how to make changes to it. And, using these methods, it's possible to create a good variety of WordPress queries. But there's still one thing that we haven't looked at. It's how to convert query arguments inside `WP_Query_Builder` into [`WP_Post`](https://codex.wordpress.org/Class_Reference/WP_Post) objects. That'll be the job of the `get_results` method below. ```
class WP_Query_Builder
{
    // ...

    /**
     * Query WordPress using the current specifications of the builder.
     *
     * @return WP_Post[]
     */
    public function get_results()
    {
        $query = new WP_Query($this->query_arguments);

        return $query->posts;
    }
}
```

As you might have noticed, `get_result` doesn't look at all like the method that we've seen up until now. It doesn't use a guard clause. In fact, it doesn't even return an object, but an array of `WP_Post` objects. This is good because you can't use method chaining with an array. This means that we're still following method cascading. `WP_Query_Builder` still only returns the current object when it returns an object. The goal with a method like `get_results` is to act as a termination point for the method chaining. You can't chain anymore because you asked for the results back. (Which makes sense!) In the case of `get_results`, the method itself isn't too complicated. We take the internal `query_arguments` array that we've been building using method cascading. We pass it as the argument for the `WP_Query` constructor. `WP_Query` will parse the given query arguments and turn them into a MySQL query. It'll then execute that query and convert the results into `WP_Post` objects. It finishes by storing these objects in the `posts` variable. That's why we return the `posts` variable. # Keeping things simple for now

At this point, we've only looked at what we'll call simple cascading methods. They had one or more guard clauses, a bit of logic and returned the current object. They also mapped to a query parameter in a straightforward manner. This isn't a bad thing! There's no way that we could look at every `WP_Query` query parameter. (There's so many!) But, because our methods follow a specific pattern, we were able to create a template method for them. And, using it, you can fill in the gaps in our domain-specific language yourself. That said, there are query parameters that don't fit the mold that we've seen today. They need a more elaborate domain-specific language. You also need more advanced fluent interface building techniques to implement them. But this is out of scope for this article. (Sorry!) For now, you can find the complete `WP_Query_Builder` class below. ```
class WP_Query_Builder
{
    /**
     * The query arguments collected by the query builder.
     *
     * @var array
     */
    private $query_arguments;

    /**
     * Constructor.
     *
     * @param array $query_arguments
     */
    public function __construct(array $query_arguments = array())
    {
        $this->query_arguments = array_merge(array(
            'no_found_rows' => true,
            'update_post_meta_cache' => false,
            'update_post_term_cache' => false,
        ), $query_arguments);
    }

    /**
     * Specify the post types that the query will retrieve.
     *
     * Can be a comma separated string or an array. Overwrites previous
     * specification criteria if called multiple times.
     *
     * @param string|array $from
     *
     * @return self
     */
    public function from($from)
    {
        if (is_string($from)) {
            $from = array_map('trim', explode(',', $from));
        } elseif (!is_array($from)) {
            return $this;
        }

        $this->query_arguments['post_type'] = $from;

        return $this;
    }

    /**
     * Query WordPress using the current specifications of the builder.
     *
     * @return WP_Post[]
     */
    public function get_results()
    {
        $query = new WP_Query($this->query_arguments);

        return $query->posts;
    }

    /**
     * Specify the maximum number of results that the query will retrieve.
     *
     * Overwrites previous specification criteria if called multiple times.
     *
     * @param int $limit
     *
     * @return self
     */
    public function limit($limit)
    {
        if (!is_numeric($limit)) {
            return $this;
        }

        $this->query_arguments['posts_per_page'] = (int) $limit;

        return $this;
    }

    /**
     * Specify the order of the query results.
     *
     * Overwrites previous specification criteria if called multiple times.
     *
     * @param string|array $sort
     * @param string $order
     *
     * @return self
     */
    public function order_by($sort, $order = 'DESC')
    {
        if (empty($sort) || (!is_array($sort) && !is_string($sort))) {
            return $this;
        } elseif (!is_string($order) || !in_array(strtoupper($order), array('ASC', 'DESC'))) {
            $order = 'DESC';
        }

        $this->query_arguments['orderby'] = $sort;
        $this->query_arguments['order'] = $order;

        return $this;
    }

    /**
     * Specify the columns that the query will retrieve.
     *
     * Overwrites previous specification criteria if called multiple times.
     *
     * @param string $select
     *
     * @return self
     */
    public function select($select)
    {
        if (empty($select) || !is_string($select)) {
            return $this;
        }

        $this->query_arguments['fields'] = $select;

        return $this;
    }
}
```