You Don't Know Query (WordCamp Netherlands 2012)

Post on 17-May-2015

37,931 views 1 download

Tags:

description

An update to a talk I gave at WordCamp Portland 2011, "You Don't Know Query" is an advanced development talk from March 25, 2012, in Utrecht, Netherlands.

transcript

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( ).

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( )

And Finally

Thanks! Questions?

@nacin