WordPress Sub-queries – The Many Options and Best Practices
Recently, while doing the new build for www.nickfleming.com, I had a situation where I wanted to show excerpts for my related post stubs on the Tag templates but not on the Single templates. Doing an is_tag() strangely returned true for all contexts.
Upon investigating I realised I was overriding the global $wp_query and with it, the context of the view and therefore the values of the conditional tags such as is_tag(), is_single() etc.
I decided to get my head around WordPress sub-queries once and for all (and tell the world!)…
Basically, you have three options when you want to do a subquery in WordPress. Each has different advantages and disadvantages. The best choice depends on the situation…
Option 1:
|
1 2 3 4 5 |
query_posts( $args ); while ( have_posts() ): the_post(); // stuff including the_title(), the_excerpt(), etc. endwhile; wp_reset_query(); |
Pros
Simplest approach. Template tags (eg the_title()) still work as expected and you can take advantage of the fact that they are filtered whereas accessing the post data directly (see option 2) skips the filters.
Cons
Replaces the global query object and the corresponding conditional tags like is_home(). If your subquery looks like a Tag template query then you might suddenly find that is_tag() == true for your homepage while is_home() gives you false
When to Use
Use this when you want to replace the default main query for the template, or when simplicity is important and conditional tags aren’t an issue.
Other Notes
Calling wp_reset_query() at the end resets the scope back to the view’s original query.
Option 2:
|
1 2 3 4 |
$some_posts = get_posts( $args ); foreach ( $some_posts as $a_post ): // output via echo $a_post['post_title'], etc. endforeach; |
Pros
Cleanest approach. No globals are altered so all conditional tags work as expected. No need to reset anything at the end. With this approach it’s possible to render multiple queries at the same time.
Cons
You’ll miss a lot of the ease and detailed functionality of the template tags. If you want the content to be handled via plugins, you’ll need to call the appropriate do_action() and do_filter() functions before outputting. This can get messy; the_content(), for example, has a “the_content” filter which it applies after calling get_the_content() which also runs a filter namesake.
You may also need to escape (esc_html(), esc_attr()) data before output.
When to Use
This method is useful for more advanced techniques such as if you want to ensure that the data remains untouched by all the plugins or if you want to merge the results of multiple queries, possibly with different (custom) posts types, into a single complex output. I’d advise only using this if you know you need it…
Option 3:
|
1 2 3 4 5 6 |
global $my_subquery; $my_subquery = new WP_Query( $args ); while( $my_subquery->have_posts() ): $my_subquery->the_post(); // output via the_title(), the_content(), etc. endwhile; wp_reset_postdata(); |
Pros
Allows the use of template tags like the_content() without affecting the conditionals like is_tag().
Cons
Still affects the global $post which could potentially confuse some plugins(?). I haven’t seen any issues yet…
When to Use
Default to this technique, especially if you are experiencing issues with Option #1.
Other Notes
$my_subquery is specified as global so subtemplates invoked via get_template_part() can access it (be sure to include the global $my_subquery line in the subtemplates too). Note, if you are using include() directly (naughty!) you need not make it global as the included file will share the invoker’s local variable scope.
Digging Deeper
For those wanting to know more, I recommend having a peak at the get_posts() and query_posts() functions to learn more about how WordPress handles it’s globals behind the scenes as will the template tags (the_excerpt()) and conditional tags (is_home()).
If you’re still thirsty then venture into the wp_reset_postdata() and wp_reset_query(). You’ll discover the global vars $post along with $wp_query and it’s esteemed cousin $wp_the_query. The later is a sort of safety net and contains the original query invoked by the requested URL, set by the get_posts() method on the main workhorse class WP. It’s what the global $wp_query resets to on a call to wp_reset_query().
