Using WP_Query Objects Without the Loop

Bookshelf WP_Query

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:

  1. Every individual object belongs to a larger class of those objects. For example, an individual Car object belongs to the class Car.
  2. Every object has things in common with other objects of its class. These commonalities break down into properties and methods.
  3. 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.
  4. Methods are things a given object can do. A Car object might have drive() and stop() 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():

WP_Query print_r

First few lines of the print_r of a WP_Query object

So 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:

  1. A list of all posts in one of our post categories, editorial,
  2. With title and a short excerpt of the content,
  3. 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:

posts_by_first_letter

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 of WP_Query is like a bookshelf, and our WP_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:

  1. Its title, and
  2. 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.)

returning 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!

Image credit: Image Catalog


4 Responses

Comments

Pingbacks