Getting Down and Dirty with WP_Query->set()

Posted on

By Michael Fields

I was so stoked when I was reading a blog post on Justin Tadlock’s site about custom post types. He introduced me to a new way of hooking into the WP_Query class and I was so excited that I had to leave a comment. I’ve been writing custom functionality into themes using this method ever since and it has really saved me some time.

…until today that is!

There is a small quirk with this method that I discovered the hard way. Here’s a bit of information about the problem I encountered with WP_Query->set() and what I did to fix it.

I needed to set the posts_per_page argument of The Loop to 1 for a specific taxonomy called “collection”. The following code accomplished this quite well.

add_filter( 'pre_get_posts', 'mfields_one_post_per_collection_view' );
function mfields_one_post_per_collection_view( $query ) {
	if ( is_tax( 'collection' ) )
		$query->set( 'posts_per_page', 1 );
	return $query;
}

In plain English this code tells WordPress that if we are viewing a term of the ‘collection’ taxonomy, it should only retrieve one post for each paged view. And quite honestly, it worked perfectly! I was stoked! Such a simple solution.

The Problem

For the project I was working on, I needed to add another query to retrieve multiple post objects. This is when things got a bit sticky. My query looked like this:

$args = array(
	'post_type' => 'piece',
	'number_posts' => 10
	);
$pieces = get_posts( $args );

I was hoping that this query would return the last 10 pieces in descending order but, no matter what modification I made to the $args array, it always returned only one result.

Why? you ask. What I came to realize is that while the call to WP_Query->set() affects The Loop – it is not the only thing that it will modify. Basically, if you use this method, you are not only hooking into the Main Loop but every other object of the WP_Query class. Using get_posts() creates a new WP_Query object so it too is affected by calls to WP_Query->set().

My Solution

I was digging around in WordPress core when I noticed that there is an argument that get_posts() always passes to the constructor of WP_Query. This argument is ‘suppress_filters’ and is used to bypass filtering of the WP_Query class. It is possible to use this arg as a condition in our logic to ensure the custom queries created from the get_posts() function will not be affected by our pre_get_posts handler.

Here is the finished code that worked well for me:

add_filter( 'pre_get_posts', 'mfields_one_post_per_collection_view' );
function mfields_one_post_per_collection_view( $query ) {
	if ( is_tax( 'collection' ) && false == $query->query_vars['suppress_filters'] ) {
		$query->set( 'posts_per_page', 1 );
	}
	return $query;
}

Hope that someone finds the useful.

6 Comments Leave a comment

  1. Justin Tadlock July 10, 2010 at 8:03 am

    Just wanted to tell you thanks for publishing this. I too ran into the same problem. I updated my post with ‘suppress_filters’, so others won’t run into the same issue.

  2. Michael Fields July 11, 2010 at 2:02 am

    No problem… glad I could add my knowledge to the pool.

  3. Mike Schinkel June 19, 2011 at 5:09 am

    Note sure if you noticed but your code has a few HTML encoding issues.

  4. Michael Fields June 19, 2011 at 6:02 am

    Thanks Mike … Wonder why this happens sometimes … it is only sometimes as other posts never get double encoded?

  5. Stephen Harris March 27, 2012 at 8:50 am

    Hi Michael – just a heads up because we had a WordPress Stack Exchange question related to this (http://wordpress.stackexchange.com/questions/45250/cannot-override-post-types-in-wp-query/45256).

    The ‘suppress_filters’ check returns true for every WP_Query query and even get_posts if it is manually set. Since 3.3 there is an is_main_query() method that can be used instead.

  6. Michael Fields March 27, 2012 at 9:00 am

    Awesome! Thanks for the link! This post is super old and should probably be deleted actually …

Share your thoughts

Fork me on GitHub