I really enjoyed David’s article from a few weeks ago on the basics of functional programming as it applies to WordPress’s nested data structures. One of the nice things about working with him is that I learn a lot, too. Today, I want to continue exploring the somewhat-functional world David introduced, and suggest some freer ways of interacting with a very important entity in WordPress: the WP_Query
object.
A WP_Query
is an Object
A
WP_Query
isn’t an abstract entity. It’s an object, in the programming sense.
When I started learning WordPress, WP_Query
was a mystical abstraction: something you invoked, like a magic spell, so that you could run functions like the_title()
and the_content()
inside a custom Loop.
However, a WP_Query
isn’t an abstract entity; rather, it’s an object, in the programming sense. We’ve written about the basics of objects and object-oriented programming before; let’s quickly summarize that discussion:
- Every individual object belongs to a larger class of those objects. For example, an individual Car object belongs to the class Car.
- Every object has things in common with other objects of its class. These commonalities break down into properties and methods.
- Properties are things about a given object. For a Car object, one property might be “color”: “color” itself is the property, and a given Car object might have “red” or “blue” as its value for that property.
- Methods are things a given object can do. A Car object might have
drive()
andstop()
methods.
Now let’s look at WP_Query
itself. Any WP_Query
object has a big list of properties and methods. A WP_Query
object’s properties contain all sorts of state and information, which are actually stored in the object itself. You can even read them directly using PHP’s print_r():

print_r
of a WP_Query objectSo our goal today is to bring WP_Query
into the world of a tangible thing that we can manipulate. I’ve set up a challenge to do something with WP_Query
that you couldn’t do just by running custom loops.
The Project: Alphabetizing by Content
Our goal with this code is to print out onto the page the following:
- A list of all posts in one of our post categories,
editorial
, - With title and a short excerpt of the content,
- Alphabetized by the first letter of the post’s content.
The third part is the tricky one, since WP_Query
has no “post content” option in the orderby
options it allows.
The final product looks as follows:
Notice that the posts are ordered not by post title, but in alphabetical order by the first words of their content.
The Code
This example’s short enough that I’m including it all in this article content:
// Tell usort what to compare
function wpshout_sort_by_content( $a, $b ) {
return strcmp(
wp_strip_all_tags( $a['post_content'] ),
wp_strip_all_tags( $b['post_content'] )
);
}
// Register the [abc_editorials] shortcode
add_shortcode( 'abc_editorials', 'wpshout_abc_editorials' );
function wpshout_abc_editorials() {
// Get our WP_Query object
$args = array(
'tax_query' => array(
array(
'taxonomy' => 'category',
'field' => 'slug',
'terms' => 'editorial',
),
),
'posts_per_page' => -1,
);
$query = new WP_Query( $args );
// Just keep the query's posts property
$posts = $query->posts;
// Get just post_title and post_content of each post
$posts = array_map(
function( $post ) {
return array(
'post_title' => $post->post_title,
'post_content' => $post->post_content,
);
},
$posts
);
// Sort by post content
usort( $posts, 'wpshout_sort_by_content' );
// Build string to return to shortcode
$return = '';
foreach( $posts as $post ) {
$return .= '<h2>' . $post[ 'post_title' ] . '</h2>';
$return .= substr(
wp_strip_all_tags( $post[ 'post_content' ] ), 0, 260
);
}
// Return built string
return $return;
}
Getting the WP_Query
Object
We start with a simple call to create a WP_Query
object containing all posts belonging to the editorial
category.
$args = array(
'tax_query' => array(
array(
'taxonomy' => 'category',
'field' => 'slug',
'terms' => 'editorial',
),
),
'posts_per_page' => -1,
);
$query = new WP_Query( $args );
If WP_Query
is new to you or the code above is unfamiliar, we’ve written about it here.
Zeroing In on the posts
Array
The WP_Query
object’s most important property, especially for our purposes, is posts
. This property contains all the information—post title, author, publication date, content, and much more—for each of the posts we fetched with our query. posts
stores all this information directly, the way a bookshelf stores books.
So the first thing we’re going to do is create a new variable, $posts
, with only the stuff in our WP_Query
that we care about, as follows:
$posts = $query->posts;
This zeroes us in just on the fetched posts themselves. (By the way, this is precisely the same thing as calling WordPress’s get_posts() function with the same $args, and saving that as $posts
.)
array_map()
-ing the posts
Array
The
posts
property ofWP_Query
is like a bookshelf, and ourWP_Post
objects are like individual books.
What does posts
have inside it? It has an array of WP_Post
objects: objects representing our individual fetched posts, each with a bunch of different properties, like post_title
, post_excerpt
, and post_content
.
So again, the posts
property of WP_Query
is rather like a bookshelf, and our WP_Post
objects are like individual books, each with their own properties.
In our case, we only care about two properties of each book:
- Its title, and
- Its content.
This is where array_map()
comes in. As David explained, array_map()
is a perfect way to take a large array (like our array of WP_Post
objects) and keep only those pieces of it we need.
$posts = array_map(
function( $post ) {
return array(
'post_title' => $post->post_title,
'post_content' => $post->post_content,
);
},
$posts
);
For every $post
in $posts
, we’re simply building a new array with two named elements: post_title
and post_content
. We then save the result of all that back to $posts
.
Sorting by Content
The next step is to sort by the first letter of our title. Since $posts
is an associative array, we’ll have to use something a bit complicated, usort()
.
I covered usort()
earlier. It asks you to write your own comparison function, which it then runs on the associative array you give it, comparing all elements of the array and putting them in order. The function we’ve written is:
Without diving too deep, this function asks “Which of the two elements’ post_content starts first in the alphabet?”
function wpShoutSortByContent( $a, $b ) {
return strcmp(
wp_strip_all_tags( $a['post_content'] ),
wp_strip_all_tags( $b['post_content'] )
);
}
After we’ve created $posts
we’ll call usort()
on it, and pass usort()
our custom comparison function, as follows:
usort( $posts, 'wpShoutSortByContent' );
This alphabetizes all our posts by content.
Building Output with foreach()
In a regular WP_Query
, we use a custom Loop and prewritten WordPress functions like the_title()
and the_content()
. But since we’ve picked apart the posts in a custom way, we can use a foreach()
loop to decide exactly what output to generate:
$return = '';
foreach( $posts as $post ) {
$return .= '<h2>' . $post[ 'post_title' ] . '</h2>';
$return .= substr(
wp_strip_all_tags( $post[ 'post_content' ] ), 0, 260
);
}
return $return;
We’re saving all this to a variable, $return
, which we then return
to the shortcode that called the function. (Writing this code into a shortcode is simply a convenient way to get it to display onto the page—if you’d like to learn more about shortcodes, read here.)
return
ing this text causes it to print out into the page content wherever the shortcode was inserted, giving us our final result.
Use WP_Query
As You Please
The point of this article is to suggest some ways of having confidence to approach WP_Query
objects—not as mysteries that somehow generate our comfort-zone Loops, but as programming objects that can be freely manipulated when the need arises. Hopefully this will help remove some barriers when WordPress’s default capabilities don’t cover your needs. Thanks for reading!
[…] Using WP_Query Objects Without the Loop […]
[…] Using WP_Query Objects Without the Loop […]
Clean! I’m always shy about using WP_Query directly since they provide an entire API (query.php) for interfacing with it procedurally. query_posts(), wp_reset_post_data(), etc.
Never sure what the “right way” is, but your approach seems simpler if one isn’t afraid of objects.
Thanks for this.