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.
2. What's 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. 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/
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. 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. 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. What can we query?
http://codex.wordpress.org/Class_Reference/WP_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. 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. 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. 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!
12. Why Subqueries?
• Add related posts
• Add child pages
• Add other types of content (eg: News)
• Widgets!
13. 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() )
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. "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 );
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. 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. 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. 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. 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. 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/
24. 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"
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. 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. 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']
){ … }
29. 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;
}
30. 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;
}
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. 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'
) );
33. 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;
}
34. 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
only
if ( $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. 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. 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. 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
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();
• Can't map to post, meta and taxonomy
39. 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!)
44. 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
45. Plugin Authors
• Consider require_once( wp-admin/install-
helper.php ) within your
register_activation_hook()
– helper functions like maybe_create_table(),
maybe_add_column()