Advanced Post Queries
-
Upload
tomauger -
Category
Technology
-
view
4.077 -
download
1
description
Transcript of Advanced Post Queries
Advanced Post QueriesBest Practices for Accessing your Data
@TomAugerWordCamp Montreal 2012
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
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/
Post Queries (code)
• query_posts( $args )• new WP_Query( $args )• get_posts( $args )• get_pages( $args )
And where does it go?
• Most often on the PHP page template• Widgets defined in plugins• Hooks declared in functions.php or
include
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’ );
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
) );
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
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
) );
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’ );
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!
Why Subqueries?
• Add related posts• Add child pages• Add other types of content (eg:
News)• Widgets!
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() )
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>
"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 );
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();}
WP_Query Examples
• http://localhost/projects/wordcamp/date-query/
• http://localhost/projects/wordcamp/sort-by-post-meta/
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 ) ) ) );
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/
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( ) ) ) );
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/
Global Query Manipulation(The Good Stuff!)
• Modify the Main Query• Modify the way all queries work site-
wide• Modify queries for a specific page
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/
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"
amovo.ca: Category Archive
• Uses the standard custom tax query• Need to ORDER BY wp_postmeta for
ALL 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}
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')) ){ // … }}
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'] ){ … }
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;}
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;}
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' )
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'
) );
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;}
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}' ";}
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!
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()
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
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
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!)
Thank You!updates: @TomAugerwww.tomauger.com
Extra Slides
• Ah, time is thy enemy…
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>
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
Plugin Authors
• Consider require_once( wp-admin/install-helper.php ) within your register_activation_hook()– helper functions like
maybe_create_table(), maybe_add_column()