setup_postdata(): The Template Tags You Need, the Custom WP_Post Arrays You Crave

thumbs up to setup_postdata() 

setup_postdata() lets you treat any bundle of posts the way you would normally treat the results of a WP_Query.

setup_postdata() is a function I really love in WordPress, because it lets you work with any bundle of posts and a foreach() loop the way you would normally work with the results of a WP_Query and a custom Loop.

This article will unpack what that statement means and why it’s cool, and give a few usage examples.

This is our last article of 2017. If you’re reading this: we made it! Where are all the flying cars?

Defining “Bundle of Posts”: Array of WP_Post Objects

In WordPress, we often discuss “bundles of posts.” In the factory analogy we use to describe WordPress overall, a bundle of posts is the fetched “raw material” that gets retrieved from the “warehouse” (database), for processing by the “factory” (theme and plugins) itself. But what is a “bundle of posts”?

In technical language, a bundle of posts is actually an array of WP_Post objects.

Array

In PHP and other programming languages, an array is a “list”: a collection of similarly structured items, like a grocery shopping list or a sports team roster.

When we say “bundle of posts” in a WordPress context, we really mean “array of posts”: a list of WP_Post objects, structured into a PHP array. It looks something like this (not valid PHP output, just to give the idea):

[
	0 => WP_Post Object 1
	1 => WP_Post Object 2
	2 => WP_Post Object 3
]

WP_Post Objects

What is the array an array of? That takes us into another major data structure: Objects.

In object-oriented programming, every Object is a member of a Class: an abstract collection of shared features that all member Objects have in common. As a real-world example, Object “My Sofa” would be a member of Class “Couch”—Couches being things that are at least three feet wide and are good to sit on, and my sofa being an example.

In WordPress, the Class we’re interested in is called WP_Post, and its Objects have the following features:

WP_Post Object (
    [ID] =>
    [post_author] =>
    [post_date] =>
    [post_date_gmt] =>
    [post_content] =>
    [post_title] =>
    [post_excerpt] =>
    [post_status] =>
    [comment_status] =>
    [ping_status] =>
    [post_password] =>
    [post_name] =>
    [to_ping] =>
    [pinged] =>
    [post_modified] =>
    [post_modified_gmt] =>
    [post_content_filtered] =>
    [post_parent] =>
    [guid] =>
    [menu_order] =>
    [post_type] =>
    [post_mime_type] =>
    [comment_count] =>
    [filter] =>
)

So the full picture we’ve got is: we’re working with arrays of WP_Post objects fetched from the database, and each object contains the properties listed above for that specific post.

Why Working Directly with Arrays of Post Objects is Useful

When you’re doing normal WordPress theming inside a regular template file, like index.php, you generally don’t need to know all the stuff we just covered. You just open the Loop, and inside it, you can use template tags like the_title() and they’ll work properly. How the post data is being fetched and stored is invisible, behind-the-scenes. The same is true for custom WP_Querys: you fetch your posts through that system, and you never really work with the WP_Post objects you’ve fetched directly.

Sometimes, though, you need to query for a bundle of posts, and then do other stuff to that bundle that you can’t do within WP_Query itself. That’s when working directly with the bundle becomes important.

An Example: Alphabetizing by Post Content

Let’s extend an example I gave in an earlier article on working with arrays of post objects. Our goal is to display all the posts from one of our post categories, “editorial,” but alphabetized by the post’s content. (So a post that starts with “A boy…” would show up early, and one that starts with “Zebras…” would show up late.)

Getting our posts from the “editorial” category is the easy part.

To do that in the other article, we wrote a custom WP_Query, and then kept just the WP_Post objects stored in the WP_Query object’s posts property (discarding the rest of the information in the WP_Query).

This time for efficiency, we’re going to use a simpler function, get_posts(), that is like WP_Query but gets only the WP_Post objects in the first place. Here’s how that looks:

 // Fetch posts
$args = array( 
	'tax_query' => array(
		array(
			'taxonomy' => 'category',
			'field'    => 'slug',
			'terms'    => 'editorial',
		),
	),
	'posts_per_page' => -1,
);
$posts = get_posts( $args );

The next thing we want to do is a bit of magic explained in the other article:

// Define sorting function
function wpshout_sort_by_content( $a, $b ) {
	return strcmp( 
		wp_strip_all_tags( $a->post_content ),
		wp_strip_all_tags( $b->post_content )
	);
}

// Sort posts
usort( $posts, 'wpshout_sort_by_content' );

And viola!

And viola

We now have an array of WP_Post objects, sorted by post content. We couldn’t have gotten here with WP_Query or get_posts() alone—we had to do some cleverness with sorting. This is where the previous tutorial stopped.

Where setup_postdata() kicks in is: How can we easily and attractively display the array of posts we’ve put together?

Why setup_postdata() is Cool: Getting to Use Template Tags Inside a foreach()

Continuing with the example above: Let’s say we now want to build out our archive page template to display the data we’ve just fetched. How do we do that? Can we use WordPress’s template tags, like the_title() and the_content(), the way we could inside the default Loop or a Loop built around a custom WP_Query?

Notice that the Loop‘s methods are expecting a single object of type WP_Query. We don’t have that at all: we’ve got an array of objects of type WP_Post. So using the Loop (which would look something like while( $posts->have_post() ) and so on) is out of the question.

foreach()ing Across Post Objects

Instead, we’ll need to use a PHP foreach() loop. This is how you iterate across a PHP array, including the ordered array of posts we’ve just created through the method in the previous section. That looks like:

foreach( $posts as $current_post ) :
	// Do something to each post, which will be called $current_post inside the loop
endforeach;

But can we use template tags? No, those are typically Loop-only. If we put, say, the_title() into our loop, it will simply return void, or else return the same, wrong title once per iteration of the foreach(). If we leave this state of affairs as it is, we’re stuck doing awkward direct references to the WP_Post objects’ properties, like:

foreach( $posts as $current_post ) :
	echo $current_post->post_title;
	echo wpautop( $current_post->post_content );
endforeach;

This has disadvantages:

  • WordPress functions, such as wpautop(), which run on template tags like the_content() automatically, won’t run unless we call them manually. This also includes escaping functions that are important for security.
  • Custom functions (from the theme and plugins) hooked to filters like the_title won’t run.
  • It’s more to write and harder to read.

setup_postdata() to the Rescue

With all that catching-up out of the way, I can now show you what’s cool about setup_postdata(). Look at the following code:

global $post; // Call global $post variable

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

	// Use template tags normally
	the_title();
	the_post_thumbnail( 'large' );
	the_excerpt();
	// Or the_content(), the_permalink(), etc.
endforeach;
wp_reset_postdata();

Cool, right? With only a few lines of voodoo, you can use template tags like the_title() (or, for that matter, the_author() or get_the_permalink())—and you can do it on an array of posts rather than a WP_Query, and inside a foreach() loop rather than the Loop that WordPress’s template files usually expect.

Again, the specific usefulness of this is when you need to fetch posts and then do further processing that WP_Query and get_posts() aren’t equipped to handle—as in our “alphabetize by content” example.

What is global $post?

Good question! WordPress stores a number of things as “global variables”: things that are true about the whole environment while WordPress’s PHP processing is running, but that aren’t necessarily visible inside a given file.

The line global $post; in PHP means: “I want to reference the PHP global variable that’s been named (somewhere else, earlier on) as $post.”

What does this variable contain? The quickest explanation is that it stores the knowledge of “which post WordPress is thinking about right now.” In other words (and without getting into detail), WordPress responds to template tags like the_title() based on which post it’s currently “thinking about,” and that information is saved into the $post global variable.

Inside the Loop, we change which post WordPress is thinking about using the_post(), or $query->the_post() for a custom query.

In this environment, invoking $post, saving $current_post to $post, and then running setup_postdata( $post ); accomplishes the same goal. We run it once each time the foreach() loop advances to the next WP_Post object—and so inside the foreach() loop, the_title() and other template tags are always pointing to the WP_Post object we’re currently working on.

After that, we run wp_reset_postdata(), as we would after a custom WP_Query, for the same reason: we want to “point” $post back to wherever it was pointing before we got involved, so we don’t mess things up down the line.

A Note on Global Variables

Does global $post feel weird to you? Because they create, basically, ghosts floating around (actually, “global $post” sounds quite a bit like “ghost,” try saying it fast), they’re generally considered bad practice. But this is how WordPress works.

Full Working Example Code and Output

Here is the code from front to back:

// Fetch "editorial" posts
$args = array( 
	'tax_query' => array(
		array(
			'taxonomy' => 'category',
			'field'    => 'slug',
			'terms'    => 'editorial',
		),
	),
	'posts_per_page' => -1,
);
$posts = get_posts( $args );

// Define sorting function
function wpshout_sort_by_content( $a, $b ) {
	return strcmp( 
		wp_strip_all_tags( $a->post_content ),
		wp_strip_all_tags( $b->post_content )
	);
}

// Sort posts
usort( $posts, 'wpshout_sort_by_content' );

// Call global $post variable
global $post;

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

	// Use template tags normally
	the_title();
	the_post_thumbnail( 'featured-image-tiny' ); 
	the_excerpt();
endforeach;
wp_reset_postdata();

Output

You could use this code in a theme or plugin—anywhere you want to output this content. A sensible thing (which I’ve skipped to keep the code demo simple) would be to register a shortcode and display the code that way.

For my purposes, I put this code (just for a moment!) in our theme’s page.php template and loaded our “About” page. Here’s what showed on the front end:

As you can see, these are the title, thumbnail-sized featured image, and excerpt of our “Editorial” posts (both the ones in the screenshot are very old!), ordered alphabetically by their content. “A couple of months ago” and “A couple of weeks ago” are the earliest-in-the-alphabet article beginnings we’ve got in that category on the site, and the page goes on from there.

Now You Understand setup_postdata()!

setup_postdata() is a very liberating function in WordPress, because it allows you to treat any bundle of WP_Post objects—no matter how you assembled it—similarly to how you treat the results of a custom WP_Query. That means easy and clean templating with WordPress’s template tags, properly functioning filter hooks, and all the rest. In other words, thanks to setup_postdata(), you’re not confined by the default fetching and sorting powers of WP_Query.

Thanks for reading!

Image credit: Ctd 2005


6 Responses

Comments

  • Awesome tutorial. I’ve actually been learning some things with a custom single.php and using global $post

    It would be great though if you could provide real working examples in your tutorials to go alongside the technical explanations.

    Thanks so much for another great tutorial. You’re explanations in understandable ways are great!

    • Fred Meyer Fred Meyer says:

      Hi Simon,

      Thanks a lot! I’ve added a section, “Full Working Example Code and Output.” Would you please let me know: Is that an example of what you mean by “real working examples,” or is there more to it than that?

      Fred

  • I enjoyed reading this article and learned a more productive route to sort my posts. One question I have is the proper way to sort by taxonomy (state). For example, I’m displaying posts currently using the WP_Query, which doesn’t include the state taxonomy within the $args because I do not want the state to be within the query. But I do want to display the state. I’m displaying the post’s corresponding state taxonomy via get_the_term_list($post->ID, ‘state’).

    My task is how to use WordPress functions to display the posts alphabetically by the state taxonomy, since the WP_Post object doesn’t include the post’s taxonomies.

    Thanks for the much needed article and I look forward to your reply.

    Happy New Year!

  • Fred Meyer Fred Meyer says:

    Hi Aisha,

    Good question! Here’s the general procedure I’d follow:
    1. Fetch the WP_Posts array.
    2. Foreach through the WP_Posts array. For each WP_Post object, get its value in the state taxonomy. Add that value onto the object as a property: $this_post->state = get_term( ... ). Save the resulting values into a new array of WP_Post objects, each now with the state property.
    3. usort() the new array by the state property of each of its objects.

    Hope that’s helpful?

Add a comment

(required)

(required)

(optional)