Alright, it’s time to continue our journey building our awesome WP_Query_Builder
class! So far, we’ve only designed it to build simple WordPress queries. But not all WordPress queries are simple or easy to model using just a cascading method.
It’s even possible that you’ve run into the limits of the WP_Query_Builder
already. The class that we created then wasn’t a complete solution. It had some serious limitations if you were a WP_Query
expert. You couldn’t use it to perform complex WordPress queries.
What do we mean by complex WordPress queries? We mean queries that use a complex WP_Query
query parameters. At the moment, there are three of them: date_query
, meta_query
and tax_query
.
So, for this article, we’re going to back to our WP_Query_Builder
class. We’ll add support for one of these complex WP_Query
query parameters. We won’t go over all three due to how complex they are. But you can apply what you’ve seen for this one query parameter to design a fluent interface for the other two.
The story so far…
But first, let’s do a small recap of what we’ve built so far. In our previous episode (linking a second time for effect), we saw two concepts. These were the fluent interface and the domain-specific language. With them, we built the WP_Query_Builder
class that you can see 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; } }
All our methods except for get_results
used a cascading method template. Using it, we created cascading methods for a few WP_Query
query parameters. This also gave you the power to create your own cascading methods.
Complex query parameters
That said, we haven’t reached the end of our story yet! As we saw earlier, WP_Query_Builder
can’t deal with complex WP_Query
query parameters. But why is that?
It’s because these query parameters are more like mini-queries. They’re not just a simple query parameter that you assign a value to. No, they’re arrays with their own unique set of query parameters. You can even nest them to form even more complex queries. (Or query inception!)
This makes it near impossible (or a bad idea at the very least) to model using a simple cascading method. We need more tools for designing fluent interfaces. And we also need to expand our domain-specific language to support them.
Focus on the “tax_query” query parameter
This is also why we won’t see all three complex WP_Query
query parameter for this article. Each of them requires in-depth analysis of their inner workings. That way we can create a relevant domain-specific language for them.
This is the hardest part of designing a fluent interface. And we can’t quite fallback on the SQL domain-specific language like we did before. This means that we have a lot more work cut out for us.
So we’re going to pick one complex WP_Query
query parameter and go with it. As you might have noticed, the section header already gave away the surprise. (Stupid section headers ruining the fun for everyone!) We’re going to go with the tax_query
query parameter. But why did we pick that specific one?
It’s because tax_query
is special. You might not know it, but all category and tag query parameters map to it. WP_Query
converts these query arguments to elements of tax_query
query argument. It then passes tax_query
to the WP_Tax_Query
class which handles taxonomy query.
The inner workings of the “tax_query” query parameter
Now, let’s dive into the tax_query
query parameter. How does it work? We need to understand that before we can begin working on our fluent interface for it.
Taxonomy array
Let’s start with the fundamental question, “What is tax_query
?” Well, it’s an array. (That was easy!) And, inside that array, there are smaller arrays which we’ll call “taxonomy arrays”.
These smaller taxonomy arrays are what we’re most interested in. They’re the main component of the tax_query
array. They each describe a section of the taxonomy query that WP_Tax_Query
will generate.
array( 'taxonomy' => 'category', 'terms' => array( 1, 4 ), 'field' => 'term_id', 'operator' => 'NOT IN', 'include_children' => false, );
Above is an example of a taxonomy array with all the possible array keys that you can use. We’ll need our expanded domain-specific language to take them all into account. So let’s look at what they do:
taxonomy
is the taxonomy that we want to query. It’s mandatory.terms
is a single or array of terms that you want to query for this taxonomy. A term can be an ID or string depending on the chosenfield
. It’s also mandatory.field
controls which database field the query will use to compare the giventerms
. The default value isterm_id
, but you can also usename
,slug
orterm_taxonomy_id
.operator
is the SQL operator that the query will use to test theterms
that you pass to it. The default value isIN
, but you can also useNOT IN
,AND
,EXISTS
andNOT EXISTS
.include_children
tells the query whether to include child taxonomies with hierarchical taxonomies or not. The default value istrue
.
Grouping and nesting taxonomy arrays
Like we mentioned earlier, tax_query
is an array of taxonomy arrays. More often than not, the tax_query
array won’t contain just one taxonomy array. It’ll contain several of them. And how you structure these taxonomy arrays inside the larger tax_query
array matters.
There are two ways to structure taxonomy arrays inside the tax_query
array. These ways of structuring taxonomy arrays are what lets you create complex taxonomy queries. Let’s take a look at them.
Grouping
Grouping is the most common structure that you’ll see with tax_query
. It comes down to grouping (thus the name!) taxonomy arrays together inside an array. WP_Tax_Query
will combine all these taxonomy arrays based on how they related to one another.
array( 'relation' => 'AND', array( 'taxonomy' => 'category', 'terms' => '1', ), array( 'taxonomy' => 'category', 'field' => 'slug', 'terms' => 'some-category-slug', 'operator' => 'NOT IN', ), );
Here’s what taxonomy array grouping looks like in practice. The first thing you might have noticed is the relation
array key. This tells WP_Tax_Query
how to join all the taxonomy arrays to build the taxonomy query.
relation
has two possible values: AND
and OR
. AND
means that the results return by WP_Query
must match all taxonomy arrays. Meanwhile, OR
means that the query results only need to match one of the taxonomy arrays. If you don’t specify a relation
, the default is AND
.
Analyzing our grouping example
Now, let’s go back to our earlier taxonomy query example. What does it translate to? Let’s break it down and look at it piece by piece.
First, there’s the relationship between taxonomy arrays. We assigned the value AND
to the relation
array key. This means that we need to match all given taxonomy arrays.
As for for the taxonomy arrays. The first one says that we want posts that have the category
with the term ID of 1
. Meanwhile, the second one is a bit more complicated because of the NOT IN
operator. It says that we want posts that don’t have the category
with the some-category-slug
slug.
Let’s finish this up by combining all the taxonomy query pieces back together. What does it say about the posts what we want WP_Query
to retrieve? We want posts assigned to the category
with the term ID of 1
. But that aren’t assigned to the category
with the some-category-slug
slug.
Nesting
The second taxonomy array structure is nesting. Nesting is when you take a group of taxonomy arrays (like the one we just saw) and you nest it inside another group of taxonomy arrays. (This is the taxonomy query inception that we were talking about!) This lets you create even more complex taxonomy queries.
$query = new WP_Query(array( 'tax_query' => array( 'relation' => 'OR', array( 'taxonomy' => 'post_format', 'field' => 'slug', 'terms' => 'some-post-format-slug', ), array( 'relation' => 'AND', array( 'taxonomy' => 'category', 'terms' => '1', ), array( 'taxonomy' => 'category', 'field' => 'slug', 'terms' => 'some-category-slug', 'operator' => 'NOT IN', ), ), ) ));
You break this taxonomy query into two parts represented by two groups of queries. You have the top level one with the 'relation' => 'AND'
. And the lower level one with the 'relation' => 'OR'
.
The lower level one is the main group of taxonomy arrays. Our query results must meet the requirements one of the two taxonomy arrays in its group. The first one is a regular taxonomy array. The other is our nested group of taxonomy arrays.
Let’s start with the regular taxonomy array. It focuses on the post_format
taxonomy. It says that a valid post must have post_format
with the some-post-format-slug
slug.
Otherwise, we have to go to our nested group of taxonomy arrays. This is the same group that we used earlier for the grouping example. It wanted posts assigned to the category
with the term ID of 1
, but not the category
with the some-category-slug
slug.
So that’s what’s going on with our taxonomy query. It’ll return posts that match the singular post_format
taxonomy array. Or it’ll return posts that match our group of category
taxonomy arrays.
Back to WP_Query_Builder
So that covers the tax_query
parameter! As you can see, there’s a good reason why we call it a complex query parameter! Using what we’ve seen, we’re going to create a fluent interface for the tax_query
. We’ll add it to our WP_Query_Builder
class.
Taxonomy array methods
If we go back to what we saw earlier, what’s the fundamental component of tax_query
? Well, it’s the taxonomy array. So, let’s start by creating a method that can generate taxonomy arrays for us.
class WP_Query_Builder { // ... /** * Generates a taxonomy array. * * @param string $taxonomy * @param array|string $terms * @param string $field * @param string $operator * @param bool $include_children * * @return array */ public function taxonomy($taxonomy, $terms, $field = 'term_id', $operator = 'IN', $include_children = true) { if (!is_array($terms)) { $terms = array($terms); } return array( 'taxonomy' => $taxonomy, 'field' => $field, 'terms' => $terms, 'operator' => $operator, 'include_children' => $include_children, ); } }
The purpose of the taxonomy
method is to generate a taxonomy array. And, like the taxonomy array, it’s the fundamental component of our fluent interface. We want it to be as generic as possible. That way, you can use it to create any taxonomy array that you need.
The method has five parameters to match the five array keys in a taxonomy array. taxonomy
and terms
are mandatory parameters. Meanwhile, field
, operator
and include_children
are optional.
The only special thing about the method is the is_array
check at the start. We use it to validate the value in the terms
variable. We want to ensure that it’s always an array. This is an optional step, but it lets us standardize the taxonomy arrays that we generate.
Creating more taxonomy array methods
There’s another reason why we made the taxonomy
method so generic. It’s because we’re going to use it as the foundation for other fluent interface methods. These methods are going to force some of the arguments passed to the taxonomy
method.
The goal of these methods is to expand our domain-specific language. If we use the taxonomy
method all the time, it makes our query less readable. This goes against one of the main purposes of the WP_Query_Builder
class.
Instead, we want to use different methods with more specific method names. That way you can know part of what’s in the taxonomy array from the method name. This, in turn, improves the readability of our query. This is the same as what WP_Query
is doing with its category and tag query parameters.
Specific taxonomy methods
An easy way to improve readability is to create taxonomy array methods for each taxonomy. This clarifies what taxonomy array WP_Query_Builder
is generating. Here’s how you’d do it for the category
taxonomy:
class WP_Query_Builder { // ... /** * Generates a taxonomy array for a category. * * @param array|string $categories * @param string $field * @param string $operator * @param bool $include_children * * @return array */ public function category($categories, $field = 'term_id', $operator = 'IN', $include_children = true) { return $this->taxonomy('category', $categories, $field, $operator, $include_children); } }
Our category
method is pretty simple. We took out the taxonomy
parameter for earlier. Instead, it always passes category
as the taxonomy
argument to the taxonomy
method.
The four category
method parameters are the same as the remaining taxonomy
method parameters. We pass them as is to the taxonomy
method. The only small difference is that we renamed the terms
parameter to categories
. This ensures that the language of the category
method is consistent.
Now, we only did this with the category
taxonomy. But it’s not too hard to extend this to any taxonomy that you need. You just need to change the taxonomy
argument that you pass to the taxonomy
method.
Specific operator methods
We can also do the same thing that we just did with the operator
parameter. We can create methods with names that clarify which operator is in the taxonomy array. We can do this for both the generic taxonomy
method and our specific taxonomy methods.
class WP_Query_Builder { // ... /** * Generates a taxonomy array where posts don't have * one of the given taxonomy terms. * * @param string $taxonomy * @param array|string $terms * @param string $field * @param bool $include_children * * @return array */ public function not_in_taxonomy($taxonomy, $terms, $field = 'term_id', $include_children = true) { return $this->taxonomy($taxonomy, $terms, $field, 'NOT IN', $include_children); } }
Let’s start with the generic taxonomy
method. The not_in_taxonomy
method also has one less parameter like the earlier category
method. But this time, we removed that operator
parameter. And instead, the method passes NOT IN
as the operator
argument.
class WP_Query_Builder { // ... /** * Generates a taxonomy array where posts don't have * one of the given categories. * * @param array|string $categories * @param string $field * @param bool $include_children * * @return array */ public function in_category($categories, $field = 'term_id', $include_children = true) { return $this->not_in_taxonomy('category', $categories, $field, $include_children); } }
We can also push this further and create specific operator methods for specific taxonomies. This is what we did with the not_in_category
method above. We used our new not_in_taxonomy
method and forced category
as the taxonomy
argument.
You could also use the category
method and force NOT IN
as the operator
argument. This is just a matter of personal preference. Use whatever makes more sense to you!
You might have noticed something about our generic taxonomy methods. They’re also specific operator methods if you keep the default value for operator
. In that scenario, using the taxonomy
method is the same as using the in_taxonomy
method. This is something that we’ll use throughout the article.
Grouping and nesting methods
Now that we’ve handled how to generate a taxonomy array, we’ll look at how we can group them together! To do this, we’ll need to create some two methods to handle that. One for each possible relation
value.
class WP_Query_Builder { // ... /** * Generates a group of query arrays using the given query arrays. The query * result must match all the given query arrays. * * @param array ...$query_arrays * * @return array */ public function and_where() { $query_arrays = func_get_args(); $query_arrays['relation'] = 'AND'; return $query_arrays; } }
The and_where
method above is the one that generates groups with AND
as the relation
. While the method is small, there are quite a bit to talk about. It wasn’t as straightforward to design as it looks.
Choosing the right method name
This might sound silly, but the hardest part of designing this method was finding a good name for it! Naming our fluent interface method well is important. We need them to make sense within the context of our domain-specific language.
The problem with this method name is that it overlaps with our specific operator methods. The taxonomy array has AND
as an operator
. So we can’t use and_taxonomy
since we need that name for the taxonomy array method.
Instead, we need to go back to our SQL domain-specific language that we’ve been using. What happens to the MySQL query generated by WP_Tax_Query
when you use grouping? Well, WP_Tax_Query
adds another condition to the the WHERE
clause of the MySQL query.
Knowing this, we could name our method and_where_taxonomy
. But is that necessary? There’s nothing taxonomy related in this method.
In fact, grouping works the same for all complex queries. You always have an array of query arrays. And within that array, there’s always a relation
array key that tells the class how to group them.
This means that we won’t need different methods for each type of complex query. We can just remove _taxonomy
from the method name. And this is how we ended up with and_where
as the method name.
Variadic function
If we look at the PHPDoc of the and_where
method, there’s something unusual going on there. There’s a @param array ...$query_arrays
in the PHPDoc, but no parameters in the method definition. What’s up with that?
Well, it’s because and_where
isn’t a normal method. It’s a variadic function. This is a type function (or method!) that can accept a variable number of arguments.
We need that because we can pass any number of query arrays to and_where
. That’s also why there are no parameters in the method definition. Instead, we use the func_get_args
to get the arguments passed to and_where
.
func_get_args
returns an array with all the arguments that and_where
received. We then assign that array to the query_arrays
variable. That’s why we can view query_arrays
as a parameter in this situation.
Grouping taxonomy arrays
So how does the and_where
method work in practice? Well, let’s convert the grouping query that we had earlier so that it uses our fluent interface. You can find the result below:
$query_builder->and_where( $query_builder->category('1'), $query_builder->not_in_category('some-category-slug', 'slug') );
You’ll notice that we use the two taxonomy array methods that we designed earlier. We pass them to the and_where
method as arguments. And the method will assign them to the query_arrays
variable using the func_get_args
function.
This is where the variadic function aspect of the and_where
method comes into play. You won’t always pass the same number of taxonomy arrays to it. It needs to be able to handle that scenario.
At this point, query_arrays
only contains taxonomy arrays that we passed as arguments. But we still need to add a relation
to the array. That’s the only other thing that the and_method
method does.
array( array( 'taxonomy' => 'category', 'field' => 'term_id', 'terms' => array('1'), 'operator' => 'IN', 'include_children' => true, ), array( 'taxonomy' => 'category', 'field' => 'slug', 'terms' => array('some-category-slug'), 'operator' => 'NOT IN', 'include_children' => true, ), 'relation' => 'AND', );
You can see the array that our and_where
method generates above. It’s a bit more verbose than the one from our earlier example. That’s just because our taxonomy array methods generate complete taxonomy arrays. They contain all the default values which we didn’t put in the array earlier.
Nesting taxonomy arrays
Now, that we’ve looked at grouping, let’s move on to nesting taxonomy arrays. The good news is that we don’t need to do anything else to support nesting. (That was easy!) So let’s look at what our nesting example from example looks like converted to a fluent interface.
$query_builder->or_where( $query_builder->taxonomy('post_format', 'some-post-format-slug', 'slug'), $query_builder->and_where( $query_builder->category('1'), $query_builder->not_in_category('some-category-slug', 'slug') ) );
The first thing that jumps out is the or_where
method. We haven’t shown any code for it so far. Let’s fix that!
class WP_Query_Builder { // ... /** * Generates a group of query arrays using the given query arrays. The query * result must match one of the given query arrays. * * @param array ...$query_arrays * * @return array */ public function or_where() { $query_arrays = func_get_args(); $query_arrays['relation'] = 'OR'; return $query_arrays; } }
There’s only a small difference between the and_where
and or_where
methods. It’s the value that gets added to the relation
key of the query_arrays
array. and_where
assigns AND
to relation
while or_where
assigns OR
to it.
Going back to the nesting example above, we pass two arguments to the or_where
method. The first one is the array generated by our generic taxonomy
method. The second one is the array generated by the and_where
method that we went over earlier.
And that’s all that you need to do to nest taxonomy arrays! This is all possible because we designed our two methods as variadic methods. The result is that it’s easy and intuitive to group and nest taxonomy arrays.
Passing our taxonomy arrays to tax_query
So far, we haven’t been interacting with WP_Query
at all. We’ve only designed methods that generated taxonomy arrays for the tax_query
query parameter. We still need a method to assign these taxonomy arrays to it.
class WP_Query_Builder { // ... /** * Specify the taxonomy of the posts that the query will retrieve. * * @param array $taxonomy_query * * @return self */ public function where_taxonomy(array $taxonomy_query) { $this->query_arguments['tax_query'] = $taxonomy_query; return $this; } }
And we’re back to designing the cascading methods that you know and love! The where_taxonomy
cascading method is quite straightforward. It follows the same format that we used throughout the previous article.
We have the taxonomy_query
parameter which the method maps to the tax_query
query parameter. Unlike our other cascading methods, there’s no guard clause in our where_taxonomy
method. That’s because we’re using type hinting to ensure that taxonomy_query
is always an array.
The choice of where_taxonomy
for our method name goes back to our earlier discussion. We mentioned that tax_query
behaved like a WHERE
clause in SQL. We didn’t name it where
because the other complex query parameters also behave that way.
Improving things
As is, our where_taxonomy
method isn’t that smart. You can’t use it without using either the and_where
or or_where
methods as well. This means that your query will always look something like this:
$query_builder = new WP_Query_Builder(); $posts = $query_builder->select('*') ->from('post') ->where_taxonomy( $query_builder->and_where( $query_builder->in_taxonomy('category', '5')), $query_builder->not_in_category('some-category-slug', 'slug') ) ) ->limit(3) ->get_results();
This is pretty lame! We shouldn’t have to use either those methods if we’re not using nesting. Instead, we should be able to do something like this as well:
$posts = $query_builder->select('*') ->from('post') ->where_taxonomy( $query_builder->category('5'), $query_builder->not_in_category('some-category-slug', 'slug') ) ->limit(3) ->get_results();
This is much more intuitive and easy to read. And it’s much more common scenario too. Most taxonomy queries don’t use nesting.
Back to the drawing board
So let’s go back to our where_taxonomy
method and fix this problem! We need to redesign our where_taxonomy
method so that it can handle both scenarios. To achieve that, we’ll have to go back to our friend func_get_args
.
Our initial version of where_taxonomy
assumed that taxonomy_query
was always a valid taxonomy array. But this won’t be the case if we redesign it as a variadic method. You might get a valid taxonomy query array or just a bunch of taxonomy arrays.
So let’s make this the new goal of our where_taxonomy
method. It should be able to convert any argument that it receives into a valid taxonomy array. This is also a great opportunity to use some of PHP’s array functions!
Why is this a good opportunity?
This is a good question and it’s worth elaborating on it. First, let’s think about what defines a variadic method. It’s the array of arguments that we get from func_get_args
.
Next, what are you trying to do with it? You’re trying to convert that array of taxonomy arrays into a single taxonomy array. And then, you want to assign that array to the tax_query
query parameter.
Well, there’s a PHP array function who’s job is to do exactly that! It’s the array_reduce
function. It lets us transform an array into a single value using a callable.
class WP_Query_Builder { // ... /** * Specify the taxonomy of the posts that the query will retrieve. * * @param array ...$taxonomies * * @return self */ public function where_taxonomy() { $this->query_arguments['tax_query'] = array_reduce(func_get_args(), array($this, 'merge_query_argument')); return $this; } }
Above is the new version of our where_taxonomy
method that uses array_reduce
. We pass it the array of arguments from func_get_args
as our array. The callable is the merge_query_argument
method that we’ll cover next.
array_reduce
will pass each argument to the merge_query_argument
method. Its job will be to merge that argument into the query array that we’re building. And this query array is what we’ll assign to the tax_query
query parameter.
A generic callable
You might have noticed that we haven’t used the term “taxonomy” for anything related to our callable. Instead, we’ve been using the term “query” to define the job of merge_query_argument
. This is an intentional choice of language.
The work that merge_query_argument
has to do isn’t specific to taxonomy queries. In fact, you can use it with any of the complex WP_Query
query parameters. This will be easier to understand once you see how it works.
class WP_Query_Builder { // ... /** * Merges the given query array argument into the given query. * * @param mixed $query * @param array $query_argument * * @return array */ private function merge_query_argument($query, array $query_argument) { if (!is_array($query)) { $query = array(); } if (!isset($query_argument['relation'])) { $query_argument = array($query_argument); $query_argument['relation'] = 'AND'; } $query['relation'] = $query_argument['relation']; unset($query_argument['relation']); foreach ($query_argument as $query_array) { $query[] = $query_array; } return $query; } }
So here’s what the merge_query_argument
method looks like. It’s not the easiest method to understand. That’s why we’ll go through it in more detail than usual. (Is that even possible!?)
Method parameters
Let’s start by looking at the parameters of our merge_query_argument
method. The first one is query
. It’s the carry that the array_reduce
function passes to our method each time it calls it. query
always contains the current query array that we’ve generated up to this point.
When it’ll first call convert_argument
, array_reduce
will pass null
as the argument for query
. That’s why there’s a guard clause checking if it’s an array at the beginning of the method. We need to always ensure that query
is an array.
The second parameter is query_argument
. This is one of the query arguments that func_get_args
returned in the where_taxonomy
method. This is what we’re trying to merge into our query
.
Managing the relation between arguments
The trickiest part of the merge_query_argument
method is how it manages the relation
array key. That’s because query_argument
can be a group of query arguments or a single one. We need to analyze it to figure it out.
This is what the second merge_query_argument
guard clause does. It checks if query_argument
has a relation
array key. If it does, we don’t need to anything. We’re dealing with the output of either out and_where
method or our or_where
method.
But if we don’t, we’re dealing with a taxonomy array. (This is just for this example. It could also be a date or meta array if you were building a query for those.) We need to reformat it so that it’s like a group of arrays.
To do that, we need to add query_argument
inside an another array. We then assign it a value for the relation
array key. By default, that value is AND
so that what we’ll use as well.
Merging the relation
Now that we’ve standardized our query_argument
with a relation
array key, what’s next? Well, we need to assign the relation
value to query
. So that’s what we do after our second guard clause.
It’s worth noting that we’re doing this in a way that we overwrite the relation
value in query
each time. This is a debatable design choice. And, in practice, it shouldn’t matter whether you do it this way or not. That’s because there isn’t a scenario where overwriting that value will cause a problem.
If you’re dealing with the output of and_where
or or_where
, there’s only one argument. Which means that you won’t enter the merge_query_argument
method a second time. So there’s no way to overwrite the relation
value.
But, with the other scenario, query_argument
will always contain a taxonomy array (in this example). And, as we discussed earlier, these taxonomy arrays will never come with a relation
value. So they will always have the value AND
assigned to them. Which means that it doesn’t matter if you overwrite it each time.
Once we’re done with the relation
array key, we want to remove it from the query_argument
array. We do this using the unset
function. The goal of this step is to simplify the logic in the last part of the method.
Merging the query argument
At this point, the only thing left to do is merge query_argument
into query
. (After all, that’s what we say the method does!) We do this by looping through all the query arrays inside query_argument
.
The loop itself just appends each query_array
to our query
. We’re able to do this because we removed the relation
array key earlier. Otherwise, we’d need to check to see if query_array
was the relation
each time.
Everything that you need
So this wraps up this second look at the WP_Query_Builder
class! You now have a lot of the tools available to you. With them, you can customize your own version of WP_Query_Builder
to your needs. After all, we’ve only covered a fraction of what WP_Query
can do.
That said, we can do a lot with what we have right now. Let’s expand our earlier nesting example with some extra WP_Query
query parameters. We’ll add some simple ones from our first article.
$query = new WP_Query(array( 'fields' => 'ids', 'post_type' => 'post', 'tax_query' => array( 'relation' => 'OR', array( 'taxonomy' => 'post_format', 'field' => 'slug', 'terms' => 'some-post-format-slug', ), array( 'relation' => 'AND', array( 'taxonomy' => 'category', 'terms' => '1', ), array( 'taxonomy' => 'category', 'field' => 'slug', 'terms' => 'some-category-slug', 'operator' => 'NOT IN', ), ), ), 'posts_per_page' => 3, )); $posts = $query->posts
Here’s the same query with our WP_Query_Builder
class:
$posts = $query_builder->select('ids') ->from('post') ->where_taxonomy( $query_builder->or_where( $query_builder->taxonomy('post_format', 'some-post-format-slug', 'slug'), $query_builder->and_where( $query_builder->category('1'), $query_builder->not_in_category('some-category-slug', 'slug') ) ) ) ->limit(3) ->get_results();
For some of you, the initial example using WP_Query
might still be easier to read. That’s ok! You can keep on doing what you were doing before.
But for some of us (myself included), this second example is the one that’s easiest the two. A lot of it has to do with the SQL domain-specific language that we’ve been using. It transforms something that we were less familiar with (WP_Query
parameters) into something that we are more (SQL).
And that’s all there is to it. It’s not better or worse. It’s just different.
You can find the complete code for the WP_Query_Builder
class here.