Advanced Post Queries

45
Advanced Post Queries Best Practices for Accessing your Data @TomAuger WordCamp Montreal 2012

description

Whether you’re maintaining a personal blog, or leveraging WordPress as your CMS backbone, you will eventually want to modify what content WordPress sends to your pages from the database. WordPress itself provides a rich set of tools and methods for interacting with the database, most of which don’t even require database expertise. Each method is appropriate in certain circumstances, and should be avoided in others. This talk discusses each method of generating or modifying the database query when and how each should be used. While this discussion focuses on coding, even those just starting out will be sure to learn best practices around how and when to use “query_posts()” vs. “get_posts()” vs. “new WP_Query()” to build custom lists of posts. Seasoned developers will appreciate discovering the right way to use hooks like “pre_get_posts”, “request” and “posts_clauses” to access the query at the lowest levels.

Transcript of Advanced Post Queries

Page 1: Advanced Post Queries

Advanced Post QueriesBest Practices for Accessing your Data

@TomAugerWordCamp Montreal 2012

Page 2: Advanced Post Queries

What's in the database?*

• Posts• Pages• Attachment records• Revisions• Nav Menus• Custom Post Types• Post Meta• Categories• Tags• Custom Taxonomies

• Users• User meta• Site Options

– Your URL– Widget settings– Theme options– User capabilities (maybe)

• * basically, everything

Page 3: Advanced Post Queries

Post Queries in the URL(look Ma, no code!)

• Settings > Reading– "Your Latest Posts"– Front page– Posts page

• Page / post slug• Category Archive• Date• Author Archive• post_type• Search

– / (index.php)– / (front-

page.php)– /posts-page-slug/

• /post-or-page-slug/• /category/term/• /year/[month/]• /author/author-nicename/• /post-type-slug/• /search/term/

Page 4: Advanced Post Queries

Post Queries (code)

• query_posts( $args )• new WP_Query( $args )• get_posts( $args )• get_pages( $args )

Page 5: Advanced Post Queries

And where does it go?

• Most often on the PHP page template• Widgets defined in plugins• Hooks declared in functions.php or

include

Page 6: Advanced Post Queries

query_posts() usageOld Skool

• Looks like URL query parameters• Mimics the browser address, so may feel more

familiar• Kinda hard to read…

query_posts( ‘cat=6&post_type=product&numberposts=5’ );

Page 7: Advanced Post Queries

query_posts() usageThe Cool Way…

• Supply arguments as array of key => value pairs• Lots more brackets and stuff, but…• Kinda looks cool once you get used to it

query_posts( array(‘cat’ => 6,‘post_type’ => ‘product’,‘posts_per_page’ => 5

) );

Page 8: Advanced Post Queries

What can we query?

• author• category / tag / tax• post name, ID(s)• page parent• post type• post status• date & time

• custom fields (meta)

• sticky• add pagination• set sort order and

criteria• comment count• even random

posts!

http://codex.wordpress.org/Class_Reference/WP_Query

Page 9: Advanced Post Queries

query_posts ExampleMovie of the Day!

• Create a new Page• Only the slug is important• Add query_posts() before any HTML• Current page content is clobbered

• http://localhost/projects/wordcamp/query_posts-example/

query_posts( array ('orderby' => 'rand','posts_per_page' => 1

) );

Page 10: Advanced Post Queries

query_posts to Limit Page Count?

• Common example is to change # posts displayed

• Loads the posts twice*!• Consider Settings > Reading > Blog

Pages Show At Most…

• * and then some…

query_posts( ‘posts_per_page=20’ );

Page 11: Advanced Post Queries

What’s up with query_posts()?

• http://codex.wordpress.org/Function_Reference/query_posts

• Only useful for altering the Main Loop• Can break pagination– (but can be fixed with effort)

• Still executes the original query!– (and then some)

• Don't use in a subquery!

Page 12: Advanced Post Queries

Why Subqueries?

• Add related posts• Add child pages• Add other types of content (eg:

News)• Widgets!

Page 13: Advanced Post Queries

Is there a template tag for that?

• http://codex.wordpress.org/Template_Tags

• Author info (the_author(), the_author_meta() )

• Metadata (the_meta() )• Categories, tags• Navigation (eg: next_post_link() )

Page 14: Advanced Post Queries

Template Tag Example

• http://localhost/projects/wordcamp/template-tags/

• Inside "The Loop", powered by the_post()

<div class="post-meta"><?php the_meta() ?></div><div class="categories"><?php the_category(); ?></div><div class="tags"><?php the_tags(); ?></div>

<div class="nav"><?php previous_post_link() ?>|<?php next_post_link() ?>

</div>

Page 15: Advanced Post Queries

"Real" Sub-queries: WP_Query

• The brains behind all queries on the wp_posts table

• Defined in wp-includes/query.php• All queries go through WP_Query• "Get your own" with

new WP_Query( $args );

Page 16: Advanced Post Queries

WP_Query usage

<?php $my_sub_query = new WP_Query( array ( 'post_type' => 'quotes', 'order' => 'rand', 'posts_per_page' => 1 ) );

while ( $my_sub_query->have_posts() ){ $my_sub_query->the_post(); get_template_part( 'my_loop_template' ); }

wp_reset_postdata();}

Page 17: Advanced Post Queries

WP_Query Examples

• http://localhost/projects/wordcamp/date-query/

• http://localhost/projects/wordcamp/sort-by-post-meta/

Page 18: Advanced Post Queries

Meta Queries

• Make your postmeta work (harder) for you!

• At first blush, a little funky

<?php $meta_query = new WP_Query( array( 'meta_query' => array( array( // meta query here ) ) ) );

Page 19: Advanced Post Queries

More Meta Query Examples

• http://localhost/projects/wordcamp/meta-queries/

• http://localhost/projects/wordcamp/multiple-meta-queries-and/

• http://localhost/projects/wordcamp/multiple-meta-queries-or/

Page 20: Advanced Post Queries

Taxonomy Queries

• As with meta_query, so with tax_query

• http://localhost/projects/wordcamp/tax-query-simple/

<?php $tax_query = new WP_Query( array( 'taz_query' => array( array( ) ) ) );

Page 21: Advanced Post Queries

wp_reset_postdata()?

• If you use the_post(), clean up after yourself

• the_post() sets lots of $_GLOBALs• Could break templates, footers,

widgets etc…

• http://localhost/projects/wordcamp/wp_reset_postdata/

Page 22: Advanced Post Queries

Global Query Manipulation(The Good Stuff!)

• Modify the Main Query• Modify the way all queries work site-

wide• Modify queries for a specific page

Page 23: Advanced Post Queries

Case Study: amovo.ca• Furniture retailer with multiple

brands• Custom post type: "amovo-products"• Custom post type: "manufacturer"• Custom taxonomies: "product-

categories" and "product-applications"

• http://localhost/projects/amovo/

Page 24: Advanced Post Queries

amovo.ca: Custom Global Sort

• Wanted to "feature" certain products• Wherever they might be listed• eg:

http://localhost/projects/amovo/product-categories/executive-conference-seating/

• Best match: custom field "_zg_priority"

Page 25: Advanced Post Queries

amovo.ca: Category Archive

• Uses the standard custom tax query• Need to ORDER BY wp_postmeta for

ALL queries

Page 26: Advanced Post Queries

Filter: 'posts_clauses'

• Allows us to manipulate SQL before it is sent to the database

• For ALL queries (not just main query)if (! is_admin()){ add_filter( 'posts_clauses', 'sort_by_priority', 10, 2 );}

function sort_by_product_priority( $clauses, $query ){ global $wpdb;

//… more to come}

Page 27: Advanced Post Queries

Keeping it lean• If query has nothing to do with

products, don’t add the overhead• Only affect product-related tax

queries

function sort_by_product_priority( $clauses, $query ){ global $wpdb;

if ( in_array( $query->query_vars['taxonomy'], array( 'product-category', 'product-application')) ){ // … }}

Page 28: Advanced Post Queries

Make sure it's the right query!

• These hooks affect EVERY query• Add conditionals to reduce overhead– is_admin() during hook registration– is_main_query()– $query->query_vars (eg: "taxonomy",

"post_type", "orderby")

if ( 'page-hijacking' == $query_obj->query_vars['pagename'] ){ … }

Page 29: Advanced Post Queries

JOIN your tables• LEFT JOIN doesn't exclude missing

meta• Alias your JOINs to avoid collision

function sort_by_product_priority( $clauses, $query ){ global $wpdb; … // JOIN on postmeta to get priority $clauses['join'] .= " LEFT JOIN {$wpdb->postmeta} PM ON ({$wpdb->posts}.ID = PM.post_id AND PM.meta_key = '_zg_priority') "; … return $clauses;}

Page 30: Advanced Post Queries

Modify ORDER BY• PREPEND your sort criteria• Don't forget to add comma if you

need one!function sort_by_product_priority( $clauses, $query ){ … $join .= "LEFT JOIN {$wpdb->postmeta} PM..."; … $orderby = &$clauses['orderby']; // by reference! if (! empty( $orderby ) ) $orderby = ', ' . $orderby;

$orderby = " IFNULL(P.meta_value, 5) ASC, {$wpdb->posts}.post_title ASC" . $orderby;

return $clauses;}

Page 31: Advanced Post Queries

Manipulating the Main Query(main filters)

• Manipulating the URI query variables– before the query is parsed– add_filter( 'request' )

• Manipulating the query object– before the SQL is generated– add_filter( 'parse_query' ) / 'pre_get_posts'

• Manipulating the SQL– before the SQL is run– add_filter( 'posts_clauses' ) 2 arguments

• Manipulating the results– before stickies and status– add_filter( 'posts_results' )

Page 32: Advanced Post Queries

Case Study: Date Range

• WP_Query doesn't (currently) support date ranges

• Let's create 'before_date' and 'after_date'

$date_query = new WP_Query( array('after_date' => '2012-08-10','before_date' => '2012-08-14'

) );

Page 33: Advanced Post Queries

Exploiting the Query Object

add_filter( 'posts_where', 'date_ranges', 10, 2 );function 'date_ranges( $where_clause, $query_obj ){ global $wpdb;

if ( $before_date = $query_obj->query['before_date'] ){ $where_clause .= " AND {$wpdb->posts}.post_date < '{$before_date}' "; }

if ( $after_date = $query_obj->query['after_date'] ) ){ $where_clause .= " AND DATE({$wpdb->posts}.post_date) > '{$after_date}' "; }

return $where_clause;}

Page 34: Advanced Post Queries

Improvements & Security• Make sure we're actually getting a

Date• Cast the time() object to a mySQL

DATE string• Cast mySQL TIMESTAMP to DATE

portion onlyif ( $after_date = strtotime( $query_obj->query['after_date'] ) ){ $after_date = date( 'Y-m-d', $after_date ); $where_clause .= " AND DATE({$wpdb->posts}.post_date) > '{$after_date}' ";}

Page 35: Advanced Post Queries

Conclusions

• TMTOWTDI• Go as high-level as you can• Use query_posts() sparingly• wp_reset_postdata()!• Modify the Main Query with filters /

actions

• Make WordPress sing!

Page 36: Advanced Post Queries

Working with $wpdb

• global $wpdb• Always use $wpdb->table_name• Always use $wpdb->prepare with

tainted data• Use the right get_* method for the job– get_var()– get_row()– get_col()– get_results()

Page 37: Advanced Post Queries

Please avoid inserting / updating…

• Posts• Users• Meta data (usermeta or postmeta)• Options• Taxonomies and Terms (especially!)

• Basically, if there's a default wp_* table, there are core methods for inserting, updating and deleting

Page 38: Advanced Post Queries

When to extend schema

• Primary object has tons of metadata• Highly relational data• Plugin tables (be sure to offer DROP

option!)– register_deactivation_hook();

• Can't map to post, meta and taxonomy

Page 39: Advanced Post Queries

Final observations… 'n stuff

• There are actually very few reasons to generate your own SQL

• There's probably a core function for that

• Due diligence and research– Google search "wordpress [keywords]"!– Get a good IDE (Jer's talk about

NetBeans!)

Page 40: Advanced Post Queries

Thank You!updates: @TomAugerwww.tomauger.com

Page 41: Advanced Post Queries
Page 42: Advanced Post Queries

Extra Slides

• Ah, time is thy enemy…

Page 43: Advanced Post Queries

More Template Tags

<div class="author-info"> <p><strong>Posted by: </strong> <?php the_author(); ?> (<?php the_author_meta( 'first_name' ) ?> <?php the_author_meta( 'last_name' ) ?>)

<?php echo apply_filters( 'the_content', get_the_author_meta( 'description' ) ); ?></div>

Page 44: Advanced Post Queries

Anatomy of a Query• $wp->main() is called after everything is loaded

– Parses the browser query string and applies rewrite rules– Rebuilds the query string for main query– Runs get_posts() on the main $wp_the_query (WP_Query instance)

• WP_Query then– Reparses the query string

• Request for single page short-circuits and uses cache if possible

– Parses the search– Parses any taxonomy queries– Generates the SQL clauses– Gives cacheing plugins a chance to get involved– Generates the SQL request and executes it!– Processes comments– Processes stickies– Updates caches

Page 45: Advanced Post Queries

Plugin Authors

• Consider require_once( wp-admin/install-helper.php ) within your register_activation_hook()– helper functions like

maybe_create_table(), maybe_add_column()