WordCamp Netherlands 2012
Andrew Nacin
Core Developer of WordPress
and Tech Ninja at Audrey Capital

@nacin on Twitter
nacin@wordpress.org
You Don’t Know Query
What do you know?
Conditional Tags


is_author( ), is_home( ), etc.
query_posts( )
Ways to query

query_posts( )
new WP_Query( )
get_posts( )
The Loop


while ( have_posts( ) ) :
  the_post( );
endwhile;
A secondary loop

$query = new WP_Query( … );
while ( $query->have_posts( ) ) :
  $query->the_post( );
endwhile;
An array of posts

$result = get_posts( … );
foreach ( $result as $post_obj ) {

}
What don’t you know?
Every query object has its
own methods
is_author( ) is the same as calling
$wp_query->is_author( )
function is_author( ) {
  global $wp_query;

    return $wp_query->is_author( );
}
With the regular loop


while ( have_posts( ) ) :
  the_post( );
  if ( is_author( ) )
     echo "An author query.";
endwhile;
With the regular loop


while ( have_posts( ) ) :
  the_post( );
  if ( $wp_query->is_author( ) )
     echo "An author query.";
endwhile;
A secondary loop

$query = new WP_Query( … );
while ( $query->have_posts( ) ) :
  $query->the_post( );
  if ( $query->is_author( ) )
     echo "An author query.";
endwhile;
A secondary loop

$query = new WP_Query( … );
while ( $query->have_posts( ) ) :
  $query->the_post( );
  if ( $query->is_author( ) )
     echo "An author query.";
endwhile;
A secondary loop

$query = new WP_Query( … );
while ( $query->have_posts( ) ) :
  $query->the_post( );
  if ( $query->is_author( ) )
     echo "An author query.";
endwhile;
If you do:
$my_query = new WP_Query( $query );

You can do:
while ( $my_query->have_posts( ) ) :
 $my_query->the_post( );
endwhile;
wp_reset_postdata( );
Why do we call functions like
wp_reset_postdata( ) and
wp_reset_query( )?

What about using query_posts( )?

How can you alter a query? How
can you alter the main query?
What is the main query,
and why should I care?
wp-blog-header.php
// Load the WordPress bootstrap
require './wp-load.php';

// Do magic
wp( );

// Decide which template files to load
require WPINC . '/template-loader.php';
Let's look in the bootstrap:

$wp_the_query = new WP_Query();
$wp_query =& $wp_the_query;
Quick lesson on PHP references
$a = 4;
$b =& $a;
$b = 2;
var_dump( $a ); // int(2)
$a = 6;
var_dump( $b ); // int(6)
So:
So the real main query is in
$wp_the_query.

And a live copy of it is stored in
$wp_query.
wp-blog-header.php
// Load the WordPress bootstrap
require './wp-load.php';

// Do magic
wp( );

// Decide which template files to load
require WPINC . '/template-loader.php';
wp-blog-header.php
// Load the WordPress bootstrap
require './wp-load.php';

// Do magic
wp( );

// Decide which template files to load
require WPINC . '/template-loader.php';
What is that wp( ) call?

function wp( $query_vars = '' ) {
  global $wp;

    $wp->main( $query_vars );
}
Holy $!@?, what just
happened?
In the bootstrap:

$wp = new WP( );

So there's a wp( ) function,
and a WP class.
class WP {
    . . .
    function main( ) {
        $this->init( );
        $this->parse_request( );
        $this->send_headers( );
        $this->query_posts( );
        $this->handle_404( );
        $this->register_globals( );
   . . .
class WP {
    . . .
    function main( ) {
        $this->init( );
        $this->parse_request( );
        $this->send_headers( );
        $this->query_posts( );
        $this->handle_404( );
        $this->register_globals( );
   . . .
WP::parse_request( )
— Parses the URL using WP_Rewrite
— Sets up query variables for WP_Query

WP::query_posts( ) {
  global $wp_the_query;
  $wp_the_query->query( $this->query_vars );
}
Boom.
SELECT SQL_CALC_FOUND_ROWS
  wp_posts.*
FROM wp_posts
WHERE 1=1
  AND wp_posts.post_type = 'post'
  AND wp_posts.post_status = 'publish'
ORDER BY wp_posts.post_date DESC
LIMIT 0, 10
wp-blog-header.php
// Load WordPress.
require './wp-load.php';

// Parse what to query. Then query it.
wp( );

// Load the theme.
require WPINC . '/template-loader.php';
Before we get to the theme,
we have your posts.

Got it?
Then why do we do this?

query_posts( 'author=-5' );
get_header( );
while( have_posts( ) ) :
  the_post( );
endwhile;
get_footer( );
That's running 2* queries!

One, the query WordPress
thought we wanted.

Two, this new one you're
actually going to use.
* Actually, WP_Query
doesn't run just one query.
It usually runs four.
1. Get me my posts:
    SELECT
    SQL_CALC_FOUND_ROWS …
    FROM wp_posts LIMIT 0, 10
2. How many posts exist?
    SELECT FOUND_ROWS( )
3. Get all metadata for these posts.
4. Get all terms for these posts.
(You can turn these off selectively…)

$my_query = new WP_Query( array(
     'no_found_rows' => true,
     'update_post_meta_cache' => false,
     'update_post_term_cache' => false,
) );
</aside>
PROTIP
‘Measure twice, cut once’
is bad for performance.
Other problems with
query_posts( )
Pagination breaks.

WordPress calculated
paging using the query it
did, not the query you did.
query_posts( array(
  'author' => -5,
  'posts_per_page' => 25,
) );

This will not work well.
You easily mess up globals.


This can break widgets and
more.
query_posts( ) is bad.

Do we agree?
Introducing pre_get_posts
class WP_Query {
   . . .
   function &get_posts() {
       $this->parse_query();
       // Huzzah!
       do_action_ref_array( 'pre_get_posts',
          array( &$this ) );
   . . .
A truly awesome hook.

function nacin_alter_home( $query ) {
  if ( $query->is_home( ) )
         $query->set( 'author', '-5' );
}
add_action( 'pre_get_posts', 'nacin_alter_home' );
Still with us?


Good, ‘cause here’s where
things get complicated.
'pre_get_posts' fires for every post
query:
 — get_posts( )
 — new WP_Query( )
 — That random recent posts widget your client
installed without you knowing.
 — Everything.
What if I just want it on the
main query?
$wp_the_query makes a
triumphant return.
Main query only!

function nacin_alter_home( $query ) {
  global $wp_the_query;
  if ( $wp_the_query === $query
             && $query->is_home() )
                  $query->set( 'author', '-5' );
}
add_action( 'pre_get_posts', 'nacin_alter_home' );
Hmm. How does this work?

$wp_the_query should never be modified. It
holds the main query, forever.

$wp_query keeps a live reference to
$wp_the_query, unless you use
query_posts( ).
query_posts( 'author=-5' );
while ( have_posts( ) ) :
  the_post( );
endwhile;
wp_reset_query( );
query_posts( 'author=-5' );
while ( have_posts( ) ) :
  the_post( );
endwhile;
wp_reset_query( );
function query_posts( $query ) {
  // Break the reference to $wp_the_query
  unset( $wp_query );
  $wp_query =& new WP_Query( $query );
  return $wp_query;
}
query_posts( 'author=-5' );
while ( have_posts( ) ) :
  the_post( );
endwhile;
wp_reset_query( );
function wp_reset_query( ) {
  // Restore reference to $wp_the_query
  unset( $wp_query );
  $wp_query =& $wp_the_query;
  // Reset the globals, too.
  wp_reset_postdata( );
}
Calling the_post( )?
  wp_reset_query( ) will reset $wp_query
  and the globals.

Calling $my_query->the_post( )?
  wp_reset_postdata( ) will reset the globals.
New in WordPress 3.3!
Rather than:
     $wp_the_query === $other_query_object
	
  
You can call:
     $other_query_object->is_main_query( )
	
  
is_main_query( ), the function, will act on
$wp_query, like any other conditional tag.
What about page
templates?
/* Template: My Template */

query_posts( $query_string .
  '&author=-5&posts_per_page=25' );

get_header( );

while ( have_posts( ) ) :
  the_post( );
endwhile;
function nacin_my_template( $query ) {
  if ( ! $query->is_main_query( ) )
      return;
  if ( ! is_page_template( 'my-template.php' ) )
      return;
  $query->set( 'author', '-5' );
  $query->set( 'posts_per_page', '25' );
}
add_action( 'pre_get_posts',
                     'nacin_my_template' );
Some Lessons
Every WP_Query object has methods that
mimic the global conditional tags.

The global conditional tags apply to
$wp_query, the main or current query.

$wp_query is always the main query, unless
you use query_posts( ). Restore it with
wp_reset_query( ).
And Finally
pre_get_posts is a powerful and flexible hook.
Just use it properly.

Always check if you're modifying the main
query using $query->is_main_query( )
Thanks! Questions?


@nacin

You Don't Know Query (WordCamp Netherlands 2012)

  • 1.
  • 2.
    Andrew Nacin Core Developerof WordPress and Tech Ninja at Audrey Capital @nacin on Twitter nacin@wordpress.org
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
    Ways to query query_posts() new WP_Query( ) get_posts( )
  • 8.
    The Loop while (have_posts( ) ) : the_post( ); endwhile;
  • 9.
    A secondary loop $query= new WP_Query( … ); while ( $query->have_posts( ) ) : $query->the_post( ); endwhile;
  • 10.
    An array ofposts $result = get_posts( … ); foreach ( $result as $post_obj ) { }
  • 11.
  • 12.
    Every query objecthas its own methods is_author( ) is the same as calling $wp_query->is_author( )
  • 13.
    function is_author( ){ global $wp_query; return $wp_query->is_author( ); }
  • 14.
    With the regularloop while ( have_posts( ) ) : the_post( ); if ( is_author( ) ) echo "An author query."; endwhile;
  • 15.
    With the regularloop while ( have_posts( ) ) : the_post( ); if ( $wp_query->is_author( ) ) echo "An author query."; endwhile;
  • 16.
    A secondary loop $query= new WP_Query( … ); while ( $query->have_posts( ) ) : $query->the_post( ); if ( $query->is_author( ) ) echo "An author query."; endwhile;
  • 17.
    A secondary loop $query= new WP_Query( … ); while ( $query->have_posts( ) ) : $query->the_post( ); if ( $query->is_author( ) ) echo "An author query."; endwhile;
  • 18.
    A secondary loop $query= new WP_Query( … ); while ( $query->have_posts( ) ) : $query->the_post( ); if ( $query->is_author( ) ) echo "An author query."; endwhile;
  • 19.
    If you do: $my_query= new WP_Query( $query ); You can do: while ( $my_query->have_posts( ) ) : $my_query->the_post( ); endwhile; wp_reset_postdata( );
  • 20.
    Why do wecall functions like wp_reset_postdata( ) and wp_reset_query( )? What about using query_posts( )? How can you alter a query? How can you alter the main query?
  • 21.
    What is themain query, and why should I care?
  • 22.
    wp-blog-header.php // Load theWordPress bootstrap require './wp-load.php'; // Do magic wp( ); // Decide which template files to load require WPINC . '/template-loader.php';
  • 23.
    Let's look inthe bootstrap: $wp_the_query = new WP_Query(); $wp_query =& $wp_the_query;
  • 24.
    Quick lesson onPHP references $a = 4; $b =& $a; $b = 2; var_dump( $a ); // int(2) $a = 6; var_dump( $b ); // int(6)
  • 25.
    So: So the realmain query is in $wp_the_query. And a live copy of it is stored in $wp_query.
  • 26.
    wp-blog-header.php // Load theWordPress bootstrap require './wp-load.php'; // Do magic wp( ); // Decide which template files to load require WPINC . '/template-loader.php';
  • 27.
    wp-blog-header.php // Load theWordPress bootstrap require './wp-load.php'; // Do magic wp( ); // Decide which template files to load require WPINC . '/template-loader.php';
  • 28.
    What is thatwp( ) call? function wp( $query_vars = '' ) { global $wp; $wp->main( $query_vars ); }
  • 29.
    Holy $!@?, whatjust happened?
  • 30.
    In the bootstrap: $wp= new WP( ); So there's a wp( ) function, and a WP class.
  • 31.
    class WP { . . . function main( ) { $this->init( ); $this->parse_request( ); $this->send_headers( ); $this->query_posts( ); $this->handle_404( ); $this->register_globals( ); . . .
  • 32.
    class WP { . . . function main( ) { $this->init( ); $this->parse_request( ); $this->send_headers( ); $this->query_posts( ); $this->handle_404( ); $this->register_globals( ); . . .
  • 33.
    WP::parse_request( ) — Parsesthe URL using WP_Rewrite — Sets up query variables for WP_Query WP::query_posts( ) { global $wp_the_query; $wp_the_query->query( $this->query_vars ); }
  • 34.
    Boom. SELECT SQL_CALC_FOUND_ROWS wp_posts.* FROM wp_posts WHERE 1=1 AND wp_posts.post_type = 'post' AND wp_posts.post_status = 'publish' ORDER BY wp_posts.post_date DESC LIMIT 0, 10
  • 35.
    wp-blog-header.php // Load WordPress. require'./wp-load.php'; // Parse what to query. Then query it. wp( ); // Load the theme. require WPINC . '/template-loader.php';
  • 36.
    Before we getto the theme, we have your posts. Got it?
  • 37.
    Then why dowe do this? query_posts( 'author=-5' ); get_header( ); while( have_posts( ) ) : the_post( ); endwhile; get_footer( );
  • 38.
    That's running 2*queries! One, the query WordPress thought we wanted. Two, this new one you're actually going to use.
  • 39.
    * Actually, WP_Query doesn'trun just one query. It usually runs four.
  • 40.
    1. Get memy posts: SELECT SQL_CALC_FOUND_ROWS … FROM wp_posts LIMIT 0, 10 2. How many posts exist? SELECT FOUND_ROWS( ) 3. Get all metadata for these posts. 4. Get all terms for these posts.
  • 41.
    (You can turnthese off selectively…) $my_query = new WP_Query( array( 'no_found_rows' => true, 'update_post_meta_cache' => false, 'update_post_term_cache' => false, ) );
  • 42.
  • 43.
    PROTIP ‘Measure twice, cutonce’ is bad for performance.
  • 44.
  • 45.
    Pagination breaks. WordPress calculated pagingusing the query it did, not the query you did.
  • 46.
    query_posts( array( 'author' => -5, 'posts_per_page' => 25, ) ); This will not work well.
  • 47.
    You easily messup globals. This can break widgets and more.
  • 48.
    query_posts( ) isbad. Do we agree?
  • 49.
    Introducing pre_get_posts class WP_Query{ . . . function &get_posts() { $this->parse_query(); // Huzzah! do_action_ref_array( 'pre_get_posts', array( &$this ) ); . . .
  • 50.
    A truly awesomehook. function nacin_alter_home( $query ) { if ( $query->is_home( ) ) $query->set( 'author', '-5' ); } add_action( 'pre_get_posts', 'nacin_alter_home' );
  • 51.
    Still with us? Good,‘cause here’s where things get complicated.
  • 52.
    'pre_get_posts' fires forevery post query: — get_posts( ) — new WP_Query( ) — That random recent posts widget your client installed without you knowing. — Everything.
  • 53.
    What if Ijust want it on the main query?
  • 54.
  • 55.
    Main query only! functionnacin_alter_home( $query ) { global $wp_the_query; if ( $wp_the_query === $query && $query->is_home() ) $query->set( 'author', '-5' ); } add_action( 'pre_get_posts', 'nacin_alter_home' );
  • 56.
    Hmm. How doesthis work? $wp_the_query should never be modified. It holds the main query, forever. $wp_query keeps a live reference to $wp_the_query, unless you use query_posts( ).
  • 57.
    query_posts( 'author=-5' ); while( have_posts( ) ) : the_post( ); endwhile; wp_reset_query( );
  • 58.
    query_posts( 'author=-5' ); while( have_posts( ) ) : the_post( ); endwhile; wp_reset_query( );
  • 59.
    function query_posts( $query) { // Break the reference to $wp_the_query unset( $wp_query ); $wp_query =& new WP_Query( $query ); return $wp_query; }
  • 60.
    query_posts( 'author=-5' ); while( have_posts( ) ) : the_post( ); endwhile; wp_reset_query( );
  • 61.
    function wp_reset_query( ){ // Restore reference to $wp_the_query unset( $wp_query ); $wp_query =& $wp_the_query; // Reset the globals, too. wp_reset_postdata( ); }
  • 62.
    Calling the_post( )? wp_reset_query( ) will reset $wp_query and the globals. Calling $my_query->the_post( )? wp_reset_postdata( ) will reset the globals.
  • 63.
    New in WordPress3.3! Rather than: $wp_the_query === $other_query_object   You can call: $other_query_object->is_main_query( )   is_main_query( ), the function, will act on $wp_query, like any other conditional tag.
  • 64.
  • 65.
    /* Template: MyTemplate */ query_posts( $query_string . '&author=-5&posts_per_page=25' ); get_header( ); while ( have_posts( ) ) : the_post( ); endwhile;
  • 66.
    function nacin_my_template( $query) { if ( ! $query->is_main_query( ) ) return; if ( ! is_page_template( 'my-template.php' ) ) return; $query->set( 'author', '-5' ); $query->set( 'posts_per_page', '25' ); } add_action( 'pre_get_posts', 'nacin_my_template' );
  • 67.
    Some Lessons Every WP_Queryobject has methods that mimic the global conditional tags. The global conditional tags apply to $wp_query, the main or current query. $wp_query is always the main query, unless you use query_posts( ). Restore it with wp_reset_query( ).
  • 68.
    And Finally pre_get_posts isa powerful and flexible hook. Just use it properly. Always check if you're modifying the main query using $query->is_main_query( )
  • 69.