Advanced Post Queries


Published on

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.

Published in: Technology, Business
  • Be the first to comment

No Downloads
Total views
On SlideShare
From Embeds
Number of Embeds
Embeds 0
No embeds

No notes for slide

Advanced Post Queries

  1. 1. Advanced Post QueriesBest Practices for Accessing your Data @TomAuger WordCamp Montreal 2012
  2. 2. Whats in the database?*• Posts • Users• Pages • User meta• Attachment records • Site Options• Revisions – Your URL• Nav Menus – Widget settings – Theme options• Custom Post Types – User capabilities• Post Meta (maybe)• Categories• Tags• Custom Taxonomies • * basically, everything
  3. 3. Post Queries in the URL (look Ma, no code!)• Settings > Reading – "Your Latest Posts" – / (index.php) – Front page – / (front-page.php) – Posts page – /posts-page-slug/• Page / post slug • /post-or-page-slug/• Category Archive • /category/term/• Date • /year/[month/]• Author Archive • /author/author-nicename/• post_type • /post-type-slug/• Search • /search/term/
  4. 4. Post Queries (code)• query_posts( $args )• new WP_Query( $args )• get_posts( $args )• get_pages( $args )
  5. 5. And where does it go?• Most often on the PHP page template• Widgets defined in plugins• Hooks declared in functions.php or include
  6. 6. query_posts() usage Old 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’ );
  7. 7. query_posts() usage The 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 ) );
  8. 8. What can we query?• author • custom fields (meta)• category / tag / tax • sticky• post name, ID(s) • add pagination• page parent • set sort order and• post type criteria• post status • comment count• date & time • even random posts!
  9. 9. query_posts Example Movie of the Day!• Create a new Page• Only the slug is important• Add query_posts() before any HTML• Current page content is clobbered query_posts( array ( orderby => rand, posts_per_page => 1 ) );• http://localhost/projects/wordcamp/query_posts-example/
  10. 10. query_posts to Limit Page Count?• Common example is to change # posts displayed query_posts( ‘posts_per_page=20’ );• Loads the posts twice*!• Consider Settings > Reading > Blog Pages Show At Most…• * and then some…
  11. 11. What’s up with 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)• Dont use in a subquery!
  12. 12. Why Subqueries?• Add related posts• Add child pages• Add other types of content (eg: News)• Widgets!
  13. 13. Is there a template tag for that?•• Author info (the_author(), the_author_meta() )• Metadata (the_meta() )• Categories, tags• Navigation (eg: next_post_link() )
  14. 14. 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>
  15. 15. "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 );
  16. 16. 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();}
  17. 17. WP_Query Examples• http://localhost/projects/wordcamp/date-query/• http://localhost/projects/wordcamp/sort-by-post-meta/
  18. 18. 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 ) ) ) );
  19. 19. 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/
  20. 20. Taxonomy Queries• As with meta_query, so with tax_query <?php $tax_query = new WP_Query( array( taz_query => array( array( ) ) ) );• http://localhost/projects/wordcamp/tax-query-simple/
  21. 21. 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/
  22. 22. Global Query Manipulation (The Good Stuff!)• Modify the Main Query• Modify the way all queries work site-wide• Modify queries for a specific page
  23. 23. Case Study:• 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/
  24. 24. 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"
  25. 25. Category Archive• Uses the standard custom tax query• Need to ORDER BY wp_postmeta for ALL queries
  26. 26. 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 }
  27. 27. 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)) ){ // … } }
  28. 28. Make sure its 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]){ … }
  29. 29. JOIN your tables• LEFT JOIN doesnt 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; }
  30. 30. Modify ORDER BY• PREPEND your sort criteria• Dont 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; }
  31. 31. 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 )
  32. 32. Case Study: Date Range• WP_Query doesnt (currently) support date ranges• Lets create before_date and after_date $date_query = new WP_Query( array( after_date => 2012-08-10, before_date => 2012-08-14 ) );
  33. 33. Exploiting the Query Objectadd_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;}
  34. 34. Improvements & Security• Make sure were 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} ";}
  35. 35. 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!
  36. 36. 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()
  37. 37. Please avoid inserting / updating…• Posts• Users• Meta data (usermeta or postmeta)• Options• Taxonomies and Terms (especially!)• Basically, if theres a default wp_* table, there are core methods for inserting, updating and deleting
  38. 38. When to extend schema• Primary object has tons of metadata• Highly relational data• Plugin tables (be sure to offer DROP option!) – register_deactivation_hook();• Cant map to post, meta and taxonomy
  39. 39. Final observations… n stuff• There are actually very few reasons to generate your own SQL• Theres probably a core function for that• Due diligence and research – Google search "wordpress [keywords]"! – Get a good IDE (Jers talk about NetBeans!)
  40. 40. Thank You!updates: @TomAuger
  41. 41. Extra Slides• Ah, time is thy enemy…
  42. 42. 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>
  43. 43. 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
  44. 44. Plugin Authors• Consider require_once( wp-admin/install- helper.php ) within your register_activation_hook() – helper functions like maybe_create_table(), maybe_add_column()