Sorting and Ordering Nested Data in WordPress: Fun with PHP Arrays and usort()

Sorted truck | sorting and ordering WordPress post data

Working with nested data structures like WP_Query‘s “objects inside arrays inside objects” can be complicated.

I’ve been doing some work for clients that involves a lot of digging around in site data, generally with either WP_Query or get_posts().

That means working with deeply nested data. A WP_Query object, for example, is a PHP object containing a number of PHP arrays, like the query array or the posts array. These arrays themselves have different structures: posts, for example, is an array of all the posts fetched in the query—each one of which is its own object, full of properties like post_title and post_content.

You don’t have to understand the above paragraph. I’m just making the point that working with nested data structures like WP_Query‘s “objects inside arrays inside objects” can be complicated. Today we’ll be working with a particular challenge nested data presents:

The Challenge: Sorting and Ordering Nested Data

The two specific complications I’ll be discussing today are sorting and ordering nested data. I’ll discuss why it’s hard, and present two specific tricks for making it easier:

  1. Creating arrays whose keys correspond to the values of a variable you wish to sort upon.
  2. Judicious use of PHP’s usort() function to sort arrays by an arbitrary property.

Example: Posts Ordered by Comment Status, Post Type, and Title

For today’s demo, I’ve given myself a requirement not too different from what my clients might dream up. I want a plugin that outputs a list of all my site’s posts (of any post type) sorted in three ways:

  • By whether or not each post accepts comments, then:
  • By post type, then:
  • Alphabetized.

The Working Plugin and What It Outputs

When I run the working plugin in my local test environment, the result looks like this:

Posts sorted by comments and post type

I’ve made the plugin’s full code available on GitHub. To use it, download the plugin, install and activate it, and use the shortcode [abc_posts_by_comment_status] anywhere in any post’s content. (Please contact me if you’re not familiar with GitHub or the “manual” way of installing plugins; I’d be happy to help.)

To follow this demo, you’ll need to be somewhat familiar with the way both PHP arrays and PHP objects work. If you need to read up on those topics, here are quick introductions to arrays and objects.

orderby Won’t Cut It

Let’s first notice that WordPress’s standard way of letting you order post results, orderby arguments, won’t get us what we need. We don’t need our fetched posts ordered by just, say, title or date; we need them consecutively ordered by three things:

  1. By comments open or closed, then
  2. By post type, then
  3. Alphabetically.

So we’re going to have to get our hands dirty. The next sections explain how.

1. Using PHP Array Keys to Sort Posts by Comment Status

The first important piece of code in the plugin is as follows:

$posts_by_comment_status = array();
foreach( $posts as $post ) {
	$posts_by_comment_status[ $post->comment_status ][] = $post;
}

For the basics of this code:

  1. It uses a PHP foreach loop. foreach( $posts as $post ) means that this loop runs once for each post in our array of all posts on the site. The current post is $post, and the array is $posts. (We retrieved this array earlier, using WordPress’s get_posts() function.)
  2. We’ve also created a new PHP array, $posts_by_comment_status; we’ll explain its purpose next.

Now let’s look more deeply at the main line, $posts_by_comment_status[ $post->comment_status ][] = $post;. Here’s a breakdown of what it means:

  1. $posts_by_comment_status is the name of the new array we created in the first line of code.
  2. [ $post->comment_status ] is a property of the current $post object. It can take one of two values, open or closed, depending on whether the current individual post allows comments or not.
  3. When we write $posts_by_comment_status[ $post->comment_status ], we are saying: create an element of the $posts_by_comment_status array. This array element’s key will be set equal to the value of $post->comment_status (so, either closed or open).
  4. [] means that the value to which this key points will be an array, and that we are going to create the “next element” (which might or might not be the first element) of that array.
  5. [] = $post; means that the “next element” of the array we’re creating is set to equal the current $post object itself.

Here’s an important thing to know: if one specific $post has a comment_status of open, and $posts_by_comment_status[open] already exists, then our $post will automatically be added to the already-existing $posts_by_comment_status[open]. In other words, the $post will simply be the “next element” of the array to which $posts_by_comment_status[open] points.

So what we get back from this is: all our original $post objects, but now separated into the closed and open elements of our new $posts_by_comment_status array. In other words, we’ve sorted all our site’s posts by whether comments are allowed!

Once we’ve got this separation, we can now talk to “comments open” and “comments closed” posts separately, as in the following lines:

$closed = wpshout_return_sorted_list( $posts_by_comment_status['closed'], 'Comments Closed:' );
$open = wpshout_return_sorted_list( $posts_by_comment_status['open'], 'Comments Open:' );

In these lines, we run a function twice, using $posts_by_comment_status['closed'] and $posts_by_comment_status['open'] as arguments. I won’t discuss the function itself thoroughly; the important part is that $posts_by_comment_status['closed'] means “An array of all our comments-closed posts,” and $posts_by_comment_status['open'] means “An array of all our comments-open posts.” Creating that separation was our first challenge, and we’ve solved it!

2. Using PHP Array Keys to Sort Posts by Post Type

Our next challenge is to take the bundle of our posts with a particular comment status (say “Closed”), and sort those posts by post type.

We’re given an array of posts, all of which have the same comment status. I’ve named this array $posts_of_same_comment_status. If you’ve understood the example above, our solution here will make perfect sense to you:

$posts_by_post_type = array();
foreach( $posts_of_same_comment_status as $post ) {
	$posts_by_post_type[ $post->post_type ][] = $post;
}

We create a new array, $posts_by_post_type, and we separate all of the posts in $posts_of_same_comment_status into different keys of that array (for example, post, page, or portfolio-element if we have that as a registered custom post type).

We’re left with $posts_by_post_type, an array with we-don’t-know-how-many keys. What we do know is that each key contains only posts of the same post type and comment status. We’ve solved our second challenge.

3. Using usort() to Sort Posts by Title

At this point, we’ve got a bundle of posts that have already been sorted by post type and comment status. Our third challenge is to order these posts alphabetically.

For this, we’re going to use PHP’s usort() function. My own introduction to usort() was through an article from Nicolas Kuttler on manually ordering WP_Query results. It’s highly recommended reading.

We’ll be using usort() as follows:

usort( $posts_of_same_post_type, 'wpshout_reorder_query_by_post_title_alphabetical' );

function wpshout_reorder_query_by_post_title_alphabetical( $a, $b ) {
	$a_title = $a->post_title;
	$b_title = $b->post_title;
    return ( strcmp($a_title, $b_title ) > 0 ) ? 1 : -1;
}

Let’s break this down:

  1. usort( $posts_of_same_post_type, 'wpshout_reorder_query_by_post_title_alphabetical' ); is running the usort() function with two arguments. $posts_of_same_post_type is the array of $post objects we just created, and 'wpshout_reorder_query_by_post_title_alphabetical' is the function we’re going to use to do the sorting.
  2. wpshout_reorder_query_by_post_title_alphabetical( $a, $b ) tells us that this function takes two arguments, named $a and $b. Each of these items are elements of the array we’re trying to sort. In other words, each one is a $post object.
  3. $a_title = $a->post_title; asks the post_title of the first element ($a), and saves it to the variable $a_title.
  4. $b_title = $b->post_title; asks the post_title of the second element ($b), and saves it to the variable $b_title.
  5. ( strcmp( $a_title, $b_title ) > 0 ) ? 1 : -1; looks like gibberish, but it’s asking: “Which of the two posts’ titles is first alphabetically?”
  6. The result is then returned back to the usort() function, which does the magic of reordering the posts according to the results it receives. This takes place across all posts in the array.
  7. The result, after at least one line of what looks like a cat pounding on a keyboard (thanks ternary operators): our posts, which were already sorted by comment status and post type, are now alphabetized! We’ve solved our third and final challenge.

    Epilogue: Outputting it All

    Since our data are highly structured and separated at this point, we’ve needed to use nested foreach loops to output them. My goal has been to show you how I sorted and ordered the data, so I won’t delve too heavily into how it’s outputted. Just know that nested foreach loops are perfectly sensible, like saying “On each week, I clean each room in the house.” If you do have any questions, I’d be happy to hear them in the comments or via email.

    Now You’re a Clever Sort!

    Hopefully this post has encouraged you to attack nested data structures like WP_Query‘s objects-holding-arrays-holding-objects and get_posts()‘s arrays-of-objects—even if you or your client need them chopped up in wacky ways. Thanks for reading!

    Image credit: allispossible.org.uk