When to use WP_query(), query_posts() and pre_get_posts

  • I read @nacin's You don't know Query yesterday and was sent down a bit of a querying rabbit hole. Before yesterday, I was (wrongly) using query_posts() for all my querying needs. Now I'm a little bit wiser about using WP_Query(), but still have some gray areas.

    What I think I know for sure:

    If I'm making additional loops anywhere on a page—in the sidebar, in a footer, any kind of "related posts", etc—I want to be using WP_Query(). I can use that repeatedly on a single page without any harm. (right?).

    What I don't know for sure

    1. When do I use @nacin's pre_get_posts vs. WP_Query()? Should I use pre_get_posts for everything now?
    2. When I want to modify the loop in a template page — lets say I want to modify a taxonomy archive page — do I remove the if have_posts : while have_posts : the_post part and write my own WP_Query()? Or do I modify the output using pre_get_posts in my functions.php file?

    tl;dr

    The tl;dr rules I'd like to draw from this are:

    1. Never use query_posts anymore
    2. When running multiple queries on a single page, use WP_Query()
    3. When modifying a loop, do this __________________.

    Thanks for any wisdom

    Terry

    ps: I have seen and read: When should you use WP_Query vs query_posts() vs get_posts()? Which adds another dimension — get_posts. But doesn't deal with pre_get_posts at all.

    @saltcod, now is different, WordPress evolved, I added few comments in comparison to the accepted answer here.

  • You are right to say:

    Never use query_posts anymore

    pre_get_posts

    pre_get_posts is a filter, for altering any query. It is most often used to alter only the 'main query':

    add_action('pre_get_posts','wpse50761_alter_query');
    function wpse50761_alter_query($query){
    
          if( $query->is_main_query() ){
            //Do something to main query
          }
    }
    

    (I would also check that is_admin() returns false - though this may be redundant.). The main query appears in your templates as:

    if( have_posts() ):
        while( have_posts() ): the_post();
           //The loop
        endwhile;
    endif;
    

    If you ever feel the need to edit this loop - use pre_get_posts. i.e. If you are tempted to use query_posts() - use pre_get_posts instead.

    WP_Query

    The main query is an important instance of a WP_Query object. WordPress uses it to decide which template to use, for example, and any arguments passed into the url (e.g. pagination) are all channelled into that instance of the WP_Query object.

    For secondary loops (e.g. in side-bars, or 'related posts' lists) you'll want to create your own separate instance of the WP_Query object. E.g.

    $my_secondary_loop = new WP_Query(...);
    if( $my_secondary_loop->have_posts() ):
        while( $my_secondary_loop->have_posts() ): $my_secondary_loop->the_post();
           //The secondary loop
        endwhile;
    endif;
    wp_reset_postdata();
    

    Notice wp_reset_postdata(); - this is because the secondary loop will override the global $post variable which identifies the 'current post'. This essentially resets that to the $post we are on.

    get_posts()

    This is essentially a wrapper for a separate instance of a WP_Query object. This returns an array of post objects. The methods used in the loop above are no longer available to you. This isn't a 'Loop', simply an array of post object.

    <ul>
    <?php
    global $post;
    $args = array( 'numberposts' => 5, 'offset'=> 1, 'category' => 1 );
    $myposts = get_posts( $args );
    foreach( $myposts as $post ) :  setup_postdata($post); ?>
        <li><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></li>
    <?php endforeach; wp_reset_postdata(); ?>
    </ul>
    

    In response to your questions

    1. Use pre_get_posts to alter your main query. Use a separate WP_Query object (method 2) for secondary loops in the template pages.
    2. If you want to alter the query of the main loop, use pre_get_posts.

    So is there any scenario when one would go straight to get_posts() rather than WP_Query?

    @drtanz - yes. Say for instance you don't need pagination, or sticky posts at the top - in these instances `get_posts()` is more efficient.

    But wouldn't that add an extra query where we could just modify pre_get_posts to modify the main query?

    @drtanz - you wouldn't be using `get_posts()` for the main query - its for secondary queries.

    In your WP_Query example, if you change $my_secondary_loop->the_post(); to $my_post = $my_secondary_loop->next_post(); you can avoid having to remember to use wp_reset_postdata() as long as you use $my_post to do what you need to do.

    @Privateer Not so, `WP_Query::get_posts()` sets `global $post;`

    @StephenHarris I just looked through the query class and don't see it. I checked mostly because I never use wp_reset_postdata because I always do queries this way. You are creating a new object and all of the results are contained within it.

    @StephenHarris Right =) If you use next_post() on the object instead of using the_post, you don't step on the global query and don't need to remember to use wp_reset_postdata afterwards.

    @Privateer Ah, my apologies, seemed to have confused myself. You are right (but you would not be able to use any functions which refer to the global `$post` e.g. `the_title()`, `the_content()`).

    True =) I never use any of those so I don't miss them.

    @urok93 I sometimes `get_posts()` when I need to get ACF related posts, especially if there's only one. Thought to standardize my templates I'm considering rewriting them as WP_Query instances.

License under CC-BY-SA with attribution


Content dated before 6/26/2020 9:53 AM