2

I am in the process of rewriting a lot of my code and restructuring a lot of my theme files to use template parts (i.e. using get_template_part()). I've come upon a situation where I want to use the same template parts whether I'm using the main query or a secondary custom query (using WP_Query). The template parts also use core functions that rely on the main query (such as conditionals).

I can get around the problem by overwriting the main query (or overwriting the $wp_query global which holds the main query, the main query still exists) with my custom query and resetting the main query once I'm done. This also means I can use the same loop for the main query and my custom queries. For example:

// Query
if ( $i_need_a_custom_query ) {
    $wp_query = new WP_Query($custom_query_args);
}

// The Loop
if ( have_posts() ) : while ( have_posts() ) : the_post();

    // Do some loop stuff
    // and call some functions that rely on the main query

// End the loop
endwhile; endif;

// I'm done so reset the query
wp_reset_query();

This works. No problem at all, but it seems a bit of a hack to me. So my question is:

  • Am I right to be weary of overwriting the main query like this?
  • Are there any side effects I'm missing?
  • And am I correct in assuming that calling wp_reset_query() isn't costly (i.e. it isn't actually re-running the query but simply resetting the globals which are still around somewhere)?

Edit to clarify:

I am only using the custom queries in question as secondary queries, that's the whole point of the question. I want to use the same template wether I am using a custom secondary query or using the main query. I understand that what I am doing is essentially the same as using query_posts(), which is a bad idea. As far as I'm aware and broadly speaking, the 2 drawbacks to using query_posts() are 1. Performance, which isn't an issue because I am only doing this when running a secondary custom query anyway and 2. Unintended consequences from changing the global $wp_query, which is exactly what I do want to happen (and as I said is actually working perfectly).

0
2

In general, I agree with Howdy_McGee that one should avoid overwriting the main query unless absolutely necessary - more often than not, modifying the main query with something like a 'pre_get_posts' hook is a better solution to such scenarios.

Manually overwriting the global $wp_query can cause all sorts of unintended behaviors if you are not exceedingly careful, including breaking pagination, among other things. It is, in essence, an even more simplistic equivalent to the often criticized query_posts() function, which the WordPress Code Reference notes:

This function will completely override the main query and isn’t intended for use by plugins or themes. Its overly-simplistic approach to modifying the main query can be problematic and should be avoided wherever possible. In most cases, there are better, more performant options for modifying the main query such as via the ‘pre_get_posts’ action within WP_Query.

In this instance however, where you require either:

  • Simultaneous access to both the main query as well as a secondary query for their distinct content and/or conditional tags
  • A generic implementation in something like a reusable template part that can be applied to either the main query or any custom query

then one solution is to store the "contextual" WP_Query instance in a local variable, and invoke WP_Query's conditional methods on that variable instead of using their globally-available conditional tag function counterparts (which always explicitly reference the main query, global $wp_query).

For instance, if you wanted your "contextual query variable" to refer to a custom secondary query in single and archive templates for your custom post type, my-custom-post-type, but refer to the main query in every other case, you could do the following:

Theme's functions.php file, or a plugin file:

function wpse_232115_get_contextual_query() {
  global $wp_query;
  static $contextual_query;

  // A convenient means to check numerous conditionals without a bunch of '||' operations,
  // i.e "if( $i_need_a_custom_query )"
  if( in_array( true,
    [
      is_singular( 'my-custom-post-type' ),
      is_post_type_archive( 'my-custom-post-type' )
    ]
  ) ) {
    // Create the contextual query instance, if it doesn't yet exist
    if( ! isset( $contextual_query ) ) {
      $query_args = [
        //...
      ];

      $contextual_query = new WP_Query( $query_args );
    }

    return $contextual_query;
  }

  return $wp_query;
}

"Generic" template-part files:

$query = wpse_232115_get_contextual_query();

// Contextual Query loop (loops through your custom query when 'my-custom-post-type's
// are being displayed, the main $wp_query otherwise.
if ( $query->have_posts() ) : while ( $query->have_posts() ) : $query->the_post();
  // Tags dependent on The Loop will refer to the contextual query if The Loop
  // was set up with the "$query->" prefix, as above. Without it, they always
  // refer to the main query.
  the_title();

  // The global conditional tags always refer to the main query
  if( is_singular() ) {
    //... Do stuff is the main query result is_singular();
  }

  // Conditional methods on the $query object reference describe either
  // the main query, or a custom query depending on the context.
  if( $query->is_archive() ) {
    //... Do stuff if the $query query result is an archive
  }
// End the loop
endwhile; endif;

I'm not sure if this is the best solution, but it's how I would think to approach the problem.

1

I feel like you should be using get_template_part() for markup. Let's say you have a Custom Template and the Blog which uses the same get_template_part(). You would call the template_part in the loop instead of calling The Loop in the part. For example:

Your Custom Template

$custom_query = new WP_Query( $args );

if ( $custom_query->have_posts() ) {
    while ( $custom_query->have_posts() ) { 
        $custom_query->the_post();
        get_template_part( $path );
    }

    wp_reset_postdata();
}

Your Blog File

if ( have_posts() ) {
    while ( have_posts() ) { 
        the_post();
        get_template_part( $path );
    }
}

This will give both your custom template and your Blog File access to variables like the_title() and the_permamlink() without necessarily having to overwrite the main query. In the end, the above will give you more flexibility.


Overwriting the Main Query is almost always a bad idea and will become a headache later. The wp_reset_query() function isn't the biggest overhead but it still does more than really necessary when dealing with secondary queries. If we look at the function itself it pretty much resets a few more globals and calls the wp_reset_postdata() function which we could just call ourselves instead.

4
  • That is how I have always used template parts but a setup like I have now is saving a lot of duplicated code, it means I can reuse the whole query and surrounding template (my example code is a very stripped back example, there is actually a lot of code outside the loop) rather than just the in-loop markup. – Cai Jul 12 '16 at 23:10
  • and although I can use functions that rely on the global $post I still can't use for example conditionals that rely on the global $wp_query – Cai Jul 12 '16 at 23:12
  • @Cai the global conditionals are little more than wrappers that invoke WP_Query methods on the global $wp_query instance. So use the proper instance methods instead of the global functions - i.e. start with $query = ( $i_need_a_custom_query ) ? new WP_Query( $custom_query_args ) : $wp_query;. Thereafter, is_singular() will refer to the main query (as per usual), and $query->is_singular() will refer to either the custom query or the main query, depending on the context. – bosco Jul 19 '16 at 3:08
  • @bosco that's exactly what I ended up doing. Instead of overwriting $wp-query I use my own var which will either reference my custom query or the main query depending on the context. I was going to write my own answer but if you want to write that as an answer I can accept that :) – Cai Jul 19 '16 at 9:11
0

What you are doing is exactly to a T what query_posts does, and it really is a bad idea. To prove my point, here is the sorce code for query_posts()

function query_posts($query) {
    $GLOBALS['wp_query'] = new WP_Query();
    return $GLOBALS['wp_query']->query($query);
}

Remember, $GLOBALS['wp_query'] === $wp_query. Now, look at your code, and you'll find it is the same.

There are more than enough writing about just how bad query_posts is, so take your time and work throught them.

There is never a valid reason to replace the main query on any page. Replacing the main query will ALWAYS lead to some issue, it might not be visible immediatly, but you'll definitely see the effect of it.

You'll also have to remember, if you are after SEO and performance, then the really bad news is that you definitely are doing it wrong then. The main query always runs normally on any page load, ie, it will query the db and return the relevant posts for the page. Simply removing the loop does NOT stop the main query. If you replace the loop with a custom one, as in your example, you are again querying the db, which means you are doing twice as much queries and also slowing the page down due to that. This negatively affect SEO, so there is something for you to think about.

To conclude, if you need something else from the main query, ALWAYS use pre_get_posts to alter it. This way you are not running any extra queries or breaking globals

5
  • None of my custom queries are to replace the main query, they are all secondary. The aim is to able to use the same template for example in archive.php and as a secondary query on a static front page. – Cai Jul 13 '16 at 8:25
  • Regardless of how I do this, the same number of queries are going to be run so that isn't an issue at all. Affecting the behaviour of thing that rely on $wp-query is exactly why I do want to do this so I'm still not sure why this is such a bad idea? – Cai Jul 13 '16 at 8:28
  • And the whole reason for this question is that I think it is a bad idea... I just can't see any practical reason why (I'm not sure how SEO comes in to it?) – Cai Jul 13 '16 at 8:32
  • @Cai WP_Query invokes considerably sizable database queries. Sizable database queries = longer page loads = lower SEO rankings. Thus, it is virtually always desirable to use a single main query where possible. People often seek to use query_posts() with custom arguments to circumvent the main query and populate their page, resulting in an unnecessary WP_Query and pointlessly taxing page load-times - this is why using pre_get_posts to alter the main query instead of letting it run then overwriting it is oft asserted (albeit your use-case is somewhat different as you want both queries) – bosco Jul 30 '16 at 18:04
  • @Cai I get where you're coming from with the question about why, though. For the most part it sounds like query_posts() should be acceptable if used responsibly. I think the most valid argument is that replacing the main-query mid-script can cause all sorts of bizarre and poorly-documented behaviors, like breaking pagination. It's difficult to use the function responsibly when you need to know every theme, plugin, and core function that's relying on the consistency of the main query in order to do so. – bosco Jul 30 '16 at 18:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.