Recently, I was having a conversation with a fellow developer via email about maintaining a separation of concerns in custom WordPress queries and WordPress templates.
The gist of the conversation boiled down to this:
Is it a good idea to keep custom queries in the template files?
A couple of years ago, I would’ve said yes but as I’ve begun to work on more complex projects, I’ve changed my mind: I’m actually a fan of keeping custom queries in functions.php
.
I think that this keeps code more maintainable, cleaner, and easier to read, though I’m not sure if this is the most common practice.
Custom WordPress Queries in Templates
In the simplest case, including a custom WordPress query is easy enough. For example, say that for a given template you want to retrieve four featured posts (based on specific post meta data).
The template and the custom query will likely look something like this:
<?php $args = array( 'post_type' => 'project', 'posts_per_page' => 4, 'meta_query' => array( array( 'key' => 'featured_project', 'value' => '1' ) ) ); $projects = new WP_Query( $args ); ?> <?php if( $projects->have_posts() ) { ?> <hr /> <div id="featured-projects"> <h3>Featured Projects</h3> <div class="row"> <?php while( $projects->have_posts() ) { ?> <?php $projects->the_post(); ?> <div class="span3 featured-project"> <h5><?php the_title(); ?></h5> <p class="featured-project-thumb"> <?php the_post_thumbnail(); ?> </p><!-- /.featured-project-thumb --> <div class="entry-content"> <?php the_excerpt( __( 'Read More...', 'lb' ) ); ?> </div><!-- /.entry-content --> </div><!-- /.featured-project --> <?php } // end while ?> <?php wp_reset_postdata(); ?> </div><!-- /.row --> </div><!-- /#featured-projects --> <?php } // end if ?>
In this case, it’s a single query and it’s not referenced anywhere else, so although this really increases the mix of PHP and HTML in the given template, its not necessarily a maintenance problem and its easy enough to read.
But if you find yourself increasing the number of custom queries in a template or you’re referencing the query in multiple places, it’s time to separate them – or, to speak proper programmerese, de-couple them – from the templates.
Separating Custom WordPress Queries
Here’s a second example from a project that I just finished. The given template is responsible for retrieving:
- The most recent 30 posts each of which are a custom post type
- A single featured post as designated by post meta data set in the dashboard
- 10 posts from featured users each of which are designated by meta data set in the dashboard and that span across multiple roles
This is clearly a more complex example as the template is executing three custom WordPress queries each of which can also be used in other parts of the application.
In this case, I think it’s a prime example of when custom queries should be abstracted into functions.php
and called by associated templates.
Reading three custom queries and their refactoring? Ain’t no body got time for that. As such, I’m going to show a single query prior to refactoring, and then a query after refactoring.
In the list of queries above, the most complicated is retrieving 10 posts from featured users who span across multiple roles, so I’ll be using that as my example.
Before
// get the featured editors $editor_query = new WP_User_Query( array( 'role' => 'editor', 'meta_key' => 'featured', 'meta_value' => 'yes', 'meta_compare' => '=', 'number' => 5 ) ); $editors = $editor_query->get_results(); // get the featured admins $administrator_query = new WP_User_Query( array( 'role' => 'administrator', 'meta_key' => 'featured', 'meta_value' => 'yes', 'meta_compare' => '=', 'number' => 5 ) ); $admins = $administrator_query->get_results(); // store them all as users $users = array_merge( $admins, $editors ); // Store the author ID's in a CSV removing the last comma $featured_user_ids = ''; foreach( $users as $user ) { $featured_user_ids .= $user->ID . ','; } // end foreach $featured_user_ids = substr( $featured_user_ids, 0, strlen( $featured_user_ids ) - 1 ); // Query for the 10 most recent posts by the specified users $args = array( 'post_type' => 'post', 'posts_per_page' => 10, 'author' => $featured_user_ids ); $the_query = new WP_Query( $args ); // Finally, loop through the posts while ( $the_query->have_posts() ) { $the_query->the_post(); /* Snipped for brevity, but this is where the markup would render post data. */ } // end while wp_reset_postdata();
It’s relatively easy to read, but it’s incredibly long. This makes makes working with the given template a bit of a hassel because you have to wade through this information prior to actually marking up the data.
By abstracting this into its own method, you can greatly simplify the foreach loop and, consequently, the template that renders this data.
After
First, you’d setup a function in functions.php
specifically for returning the query. Obviously this will look exactly the same as above – it’s simply encapsulating the calls to the WordPress API:
function example_get_featured_user_posts() { // get the featured editors $editor_query = new WP_User_Query( array( 'role' => 'editor', 'meta_key' => 'featured', 'meta_value' => 'yes', 'meta_compare' => '=', 'number' => 5 ) ); $editors = $editor_query->get_results(); // get the featured admins $administrator_query = new WP_User_Query( array( 'role' => 'administrator', 'meta_key' => 'featured', 'meta_value' => 'yes', 'meta_compare' => '=', 'number' => 5 ) ); $admins = $administrator_query->get_results(); // store them all as users $users = array_merge( $admins, $editors ); // Store the author ID's in a CSV removing the last comma $featured_user_ids = ''; foreach( $users as $user ) { $featured_user_ids .= $user->ID . ','; } // end foreach $featured_user_ids = substr( $featured_user_ids, 0, strlen( $featured_user_ids ) - 1 ); // Query for the 10 most recent posts by the specified users $args = array( 'post_type' => 'post', 'posts_per_page' => 10, 'author' => $featured_user_ids ); $the_query = new WP_Query( $args ); return $the_query; } // end example_get_featured_user_posts
Next, you’d then make a call to this function in the foreach
to render the data:
$users_query = example_get_featured_user_posts(); while( $users_query->have_posts() ) { $users_query->the_post(); /* Snipped for brevity, but this is where the markup would render post data. */ } // end while wp_reset_postdata();
Technically, I think you can make further improvements by breaking the query functionality into multiple functions, but that’s a whole other post.
Better, But Best Practice?
When it comes to custom WordPress queries, I almost always keep them in functions.php
and then call them from templates even if I rarely call them. I find that it generally makes for strong separation of concerns and keeps the template code cleaner.
At the very least, I think that this should be done whenever a custom WordPress query is called multiple times throughout a theme, plugin, or application, or if a custom query is relatively complicated.
The thing is, I’m not certain that this is considered a best practice or not. I’ve found it to be useful in my efforts, but I’m curious as to where the rest of you stand.
Leave a Reply
You must be logged in to post a comment.