How to Sort Posts by their Taxonomy Terms

Sorted truck | sorting and ordering WordPress post data

In our recent article on setup_postdata(), a reader posted a good question: how to sort a fetched array of WP_Post objects alphabetically by their terms in a given taxonomy?

(Happy New Year, by the way! We’re coming straight out the gate with some pretty nerdy stuff.)

In other words, if I’m working with posts of type Movie, and they’re organized in a taxonomy called Genre, then how do I output first the Movies in “Action,” and then “Comedy,” and then “Romance”—in one loop, and regardless of the actual titles of the posts themselves?

The current top answer in Google for “sort posts by taxonomy value” uses multiple get_posts() calls within a foreach() loop. I don’t love that solution because it implies working with multiple arrays of WP_Post objects—rather than one, sorted array that we can do whatever we want with.

Doing it in one loop, across a single sorted array of WP_Posts, is a fair amount of code, but there is a good solution. That’s what we cover today.

Demo of Posts Sorted by Taxonomy Terms

This demo comes from a test site, which has two posts in two different categories:

The test site is running Twenty Seventeen. We pasted the entirety of the code from the “Full Working Code” section below into Twenty Seventeen’s index.php template, where The Loop would normally go, as follows (see line 34):

That gives this result on the demo site’s homepage:

 

This is special because it’s sorting these posts alphabetically by the name of the category they’re in. If the posts were being sorted either alphabetically by their own names, or reverse-chronologically by their publication dates (as in the “Recent Posts” widget on the right), then they’d appear in the opposite order.

Sorting an Array of Posts by their Taxonomy Terms: Full Working Code

The code below does the following:

  1. Fetches all posts (of type post by default) that have a value for the category taxonomy.
  2. Sorts those posts alphabetically by the name of the first category they’re listed in.
  3. Outputs the permalinks and titles of each post in their sorted order.
function wpshout_fetch_posts_in_category_taxonomy() {
	// Fetch posts that have a value for the 'category' taxonomy
	$args = array(
		'tax_query' => array(
			array(
				'taxonomy' => 'category',
				'operator' => 'EXISTS',
			)
		)
	);

	// Return fetched posts
	return get_posts( $args );
}

// Customize each of the fetched WP_Post objects: each will have a
// 'category' property containing the WP_Term object of its first category
function wpshout_add_category_term_objects_to_posts( $posts ) {
	foreach( $posts as $post_index => $current_post ) :
		// Get array of WP_Term category terms for the current post
		$terms = get_the_terms( $current_post, 'category' );
		// Save the first WP_Term object to the WP_Post object
		$current_post->category = $terms[0];
		// Update the $posts array with the modified WP_Post object
		$posts[$post_index] = $current_post;
	endforeach;

	// Return array of modified WP_Post objects
	return $posts;
}

// Define sorting function to sort by category name
function wpshout_sort_posts_by_category( $a, $b ) {
	return strcmp( 
		wp_strip_all_tags( $a->category->name ),
		wp_strip_all_tags( $b->category->name )
	);
}

// Call just-registered functions to get sorted array of WP_Post objects,
// then output with foreach()
function wpshout_output_posts_sorted_by_category() {
	$posts = wpshout_fetch_posts_in_category_taxonomy();

	// Return if no results
	if( ! is_array( $posts ) ) :
		return false;
	endif;

	// Add category WP_Term object as a property to each WP_Post object
	$posts = wpshout_add_category_term_objects_to_posts( $posts );

	// Sort posts by category name
	usort( $posts, 'wpshout_sort_posts_by_category' );

	// Call global $post variable
	global $post;

	// Loop through sorted posts and display using template tags
	foreach( $posts as $current_post ) :
		// Set $post global variable to the current post object   
		$post = $current_post; 
		// Set up "environment" for template tags
		setup_postdata( $post );

		// Use template tags normally
		echo '<div><a href="' . get_the_permalink() . '">';
			the_title();
		echo '</a>';
	endforeach;

	// Reset global $post variable
	wp_reset_postdata();
}

// Call the outputting function wherever you want in your template file
wpshout_output_posts_sorted_by_category();

Notes on the Code

There are a few key points to notice here.

Adding a category Property to the WP_Post Objects

As the “>original question mentioned, taxonomy terms are not a default property of a WP_Post object. That’s why sorting on them is more of a challenge than sorting by the posts’ names, publication dates, and so on.

We fixed that issue by bolting on another object—a WP_Term object—giving all sorts of information about the first category of which each post is a member. A sample WP_Term object looks like this:

object(WP_Term)#1143 (10) {
	["term_id"]=>
	int(15)
	["name"]=>
	string(14) "First Category"
	["slug"]=>
	string(14) "first-category"
	["term_group"]=>
	int(0)
	["term_taxonomy_id"]=>
	int(15)
	["taxonomy"]=>
	string(8) "category"
	["description"]=>
	string(0) ""
	["parent"]=>
	int(0)
	["count"]=>
	int(1)
	["filter"]=>
	string(3) "raw"
}

Each WP_Post object, through the wpshout_add_category_term_objects_to_posts() function, had a WP_Term object added as its new category property.

usort() by Category Name

With the step above completed, we could then use the name property of the category property of each post. Getting the category name from a modified WP_Post object looks like: $this_post->category->name.

That enabled us to do an alphabetical ordering of posts by their category names using usort(). We touch a bit more on usort() itself in our article on setup_postdata().

Outputting Content

To use an array of WP_Post objects like we would normally use a WP_Query object, we again make use of WordPress’s lovely setup_postdata() function.

Where to Use This Code

You can use this code in a plugin or a theme template file: anywhere where you need to fetch posts, order them by a taxonomy term, and output them onto the page.

Our quick demo above simply pasted the full demo code into a theme’s index.php. You could also put everything but the final line into your theme’s functions.php and then call wpshout_output_posts_sorted_by_category() anywhere in your theme you want, or register a widget that uses this functionality, and so on.

Changing Arguments for get_posts()

As our guides to WP_Query and get_posts() cover, you can customize your get_posts() arguments as you like. The below example changes the targeted post type to product, limits the fetched results to the five most recently published posts, and changes the taxonomy to be sorted to product_cat.

'post_type' => 'product',
'posts_per_page' => 5,
'tax_query' => array(
	array(
		'taxonomy' => 'product_cat',
		'operator' => 'EXISTS',
	)
)

The Drawback of this Way of Sorting Post Objects

The thing I wish I could change is the number of separate database queries: in addition to the single get_posts() call, there’s one get_the_terms() call per fetched post. If your get_posts() call fetched a thousand posts, then that’s 1,001 extra trips to the database, which is going to hurt from a performance standpoint.

I’d be interested to hear if anyone knows of an improvement for this. One idea would be to write a single, cached query that fetches the taxonomy terms for the targeted taxonomy for all posts, and then just reference the results of that query in the rest of the code but I’m not sure how that’d work in practice.

Your Wish is Our Command

So, we do read the comments. :) Understanding how to sort posts by their metadata—including their taxonomy terms—is also a pretty helpful thing to know how to do in WordPress, so hopefully you’ve understood the demo above and how it could be altered and extended. If so, congratulations, you’re Pretty Good in WordPress.

Thanks for reading! Let’s hear your questions and comments below.

Image credit: allispossible.org.uk


2 Responses

Comments

Add a Comment

Your email address will not be published. Required fields are marked *