1 of 43

A Deep Dive into

WordPress Loops

Micah Wood

@wpscholar

wpscholar.com

2 of 43

What is the WordPress Loop?

3 of 43

The Loop iterates over each post retrieved and sets up the context for template tags.

4 of 43

The Loop - Pseudo Code

If we have one or more posts:

For each post:

  • Set up the post context for template tags
  • Display the results using template tags

Otherwise:

Show a ‘No Posts Found’ message

5 of 43

WordPress Loop Examples

6 of 43

Standard Loop

<?php

if ( have_posts() ) :

while ( have_posts() ) :

the_post();

the_content();

endwhile;

endif;

7 of 43

Standard Loop

<?php

if ( have_posts() ) :

while ( have_posts() ) :

the_post();

the_content();

endwhile;

endif;

8 of 43

have_posts()

function

Determines whether there are more posts available in the loop.

9 of 43

the_post()

function

Moves to the next post and sets up global post data.

10 of 43

Standard Loop with Conditional

<?php

if ( have_posts() ) :

while ( have_posts() ) :

the_post();

the_content();

endwhile;

else:

_e(‘No posts found.’, ‘text-domain’);

endif;

11 of 43

Custom Query Loop

<?php

$query = new WP_Query({query args});

if ( $query->have_posts() ) :

while ( $query->have_posts() ) :

$query->the_post();

the_content();

endwhile;

endif;

wp_reset_postdata();

12 of 43

Custom Query Loop

<?php

$query = new WP_Query({query args});

if ( $query->have_posts() ) :

while ( $query->have_posts() ) :

$query->the_post();

the_content();

endwhile;

endif;

wp_reset_postdata();

13 of 43

WP_Query

class

Handles requesting posts from the database.

14 of 43

wp_reset_postdata()

function

Restores the global post and sets up the global post data.

15 of 43

Custom Collection Loop

<?php

global $post;

$posts = get_posts();

if ( $posts ) :

foreach ( $posts as $post ) :

setup_postdata( $post );

the_content();

endforeach;

endif;

wp_reset_postdata();

16 of 43

Custom Collection Loop

<?php

global $post;

$posts = get_posts();

if ( $posts ) :

foreach ( $posts as $post ) :

setup_postdata( $post );

the_content();

endforeach;

endif;

wp_reset_postdata();

17 of 43

get_posts()

function

Returns an array of posts based on the provided arguments.

Note: Bypasses query filters by default.

18 of 43

setup_postdata()

function

Sets up the global post data.

19 of 43

The Anti-Loop

<?php

the_post();

the_content();

20 of 43

The Evil Loop

<?php

query_posts({query args});

if ( have_posts() ) :

while ( have_posts() ) :

the_post();

the_content();

endwhile;

endif;

wp_reset_query();

21 of 43

query_posts()

function

A function you should never use.

Runs a custom query and overrides the global query after WordPress has already run the global query.

22 of 43

wp_reset_query()

function

Another function you should never have to use.

Only used to properly clean up after query_posts().

23 of 43

Why query_posts() is Evil

query_posts() function

  • Runs a second query
  • Replaces global WP_Query
  • Requires calling wp_reset_query
  • Requires extra work to get pagination to work

pre_get_posts hook

  • Runs one query
  • Alters global WP_Query
  • Requires no alterations to theme templates
  • Pagination works automatically

24 of 43

pre_get_posts

hook

An action hook that allows you to manipulate a query for posts before it is run.

25 of 43

Customizing the Global Query

<?php

add_action(‘pre_get_posts’, function( $query ){

if( !is_admin() && $query->is_main_query() ) {

if( $query->is_tax( ‘category’ ) ) {

$query->set( ‘posts_per_page’, 15 );

}

}

});

26 of 43

27 of 43

Custom WordPress Loop Optimizations

28 of 43

Rule #1

Always restrict the number of posts returned.

29 of 43

Always restrict the number of posts returned

<?php

$query = new WP_Query([

‘posts_per_page’ => 100, // Good

‘posts_per_page’ => -1, // Bad

]);

30 of 43

Rule #2

Limit the number of queries run when possible.

31 of 43

Queries Run by WP_Query

  • A query to fetch the posts.
  • A query to fetch the total number of matching posts.
  • A query to fetch the post meta for the posts.
  • A query to fetch taxonomy and term data for the posts.
  • A query to fetch sticky posts (only on blog page)

32 of 43

Limit the number of queries when possible

<?php

$query = new WP_Query([

// Add if sticky posts aren’t needed

‘ignore_sticky_posts’ => true,

]);

33 of 43

Limit the number of queries when possible

<?php

$query = new WP_Query([

// Add if pagination isn’t needed

‘no_found_rows’ => true,

]);

34 of 43

Limit the number of queries when possible

<?php

$query = new WP_Query([

// Add if post meta isn’t needed

‘update_post_meta_cache’ => false,

]);

35 of 43

Limit the number of queries when possible

<?php

$query = new WP_Query([

// Add if term data isn’t needed

‘update_post_term_cache’ => false,

]);

36 of 43

Limit the number of queries when possible

<?php

$query = new WP_Query([

// Add if post and term data isn’t needed

‘cache_results’ => false,

]);

37 of 43

Rule #3

Limit the number of joins in the query.

38 of 43

Limit the number of joins when possible

<?php

$query = new WP_Query([

// Each meta query dimension adds a join

‘meta_query’ => [[

‘key’ => ‘color’,

‘value’ => ‘blue’,

]],

]);

39 of 43

Limit the number of joins when possible

<?php

$query = new WP_Query([

// Each tax query dimension adds a join

‘tax_query’ => [[

‘taxonomy’ => ‘people’,

‘field’ => ‘name’,

‘terms’ => ‘Bob’,

]],

]);

40 of 43

Rule #4

Only return the required fields.

41 of 43

Only return the required fields

<?php

$query = new WP_Query([

// Only return the post IDs

‘fields’ => ‘ids’,

]);

$ids = $query->posts; // Array of post IDs

// NOTE:

// Looping through this query will trigger

// a new query for each post via get_post().

42 of 43

A Deep Dive into

WordPress Loops

Micah Wood

@wpscholar

wpscholar.com

43 of 43

Resources