WP Gotcha #1: Passing an empty array as a WP_Query parameter

With this article, I would like to start a series called WP Gotchas. In it, I will describe some problems you might and probably will run into as a WordPress plugin developer – if you haven’t already.

At one point I was caught off-guard by them. Maybe this post will help you if you ever run into one of these issues.

This article is split into the following sections:

  1. Problem description
  2. Why does this happen?
  3. How to solve it in my code?
  4. Takeaways from this issue
  5. Further reading

Problem description

If you pass an empty array as some of the parameters in WP_Query, it might result in it applying no filtering for these parameters at all.

One such parameter is 'post__in' that tells WordPress to only return posts whose IDs match one of the given IDs. For example:

<?php

/* The query below will return posts that have an ID of either 1, 2 or 3. */

$id_one_two_or_three = new WP_Query( array(
    'post__in' => array( 1, 2, 3 ),
) );

/* The query below will return the post with ID = 1. */

$id_one = new WP_Query( array(
    'post__in' => array( 1 ),
) );

/*
 * The query below will not apply post ID filtering at all,
 * meaning it will return the 10 most recent posts by default.
 *
 * This is the problematic case (the post ID list is an empty array).
 */

$empty_id_list = new WP_Query( array(
    'post__in' => array(),
) );

Why does this happen?

WP_Query’s get_posts() method does this when checking if a 'post__in' parameter was passed:

// If a post number is specified, load that post
if ( $q['p'] ) {
	// ...
} elseif ( $q['post__in'] ) {
	$post__in = implode( ',', array_map( 'absint', $q['post__in'] ) );
	$where   .= " AND {$wpdb->posts}.ID IN ($post__in)";
} elseif ( $q['post__not_in'] ) {
	// ...
}

As per this article in the PHP docs, you can see that an empty array is treated as false when converting to boolean. This is called a “falsey” value.

This means that if you pass an empty array as the 'post__in' parameter, WP_Query will think: “all right, nothing was passed as the 'post__in' parameter!” and disregard the 'post__in' parameter.

How to solve it in my code?

1. If your code is running the query

The most elegant idea I found online (unfortunately, I can no longer find the URL where I found it) is to not do the query at all in situations where no posts should be returned.

If it is your code that is calling the query, you can add a check to your function checking if 'post__in' is empty before calling the query. If it is, you can return an empty array instead.

So instead of doing this:

<?php

$my_posts = get_posts(
	'post__in' => $some_list_of_post_ids,
);

You can wrap the query in a function that will check if what will be passed to 'post__in' is empty. Let’s call this function get_posts_by_ids():

<?php

function get_posts_by_ids( $post_ids ) {
	if ( empty( $post_ids ) ) {
		// Return an empty array to prevent the query from executing.
		return array();
	}

	return get_posts(
		'post__in' => $post_ids,
	);
}

$my_posts = get_posts_by_ids( $some_list_of_post_ids );

This is the best solution because the query is only constructed and called when it is actually needed so there is no unnecessary overhead.

2. If your code is not running the query but you can affect the parameters passed to WP_Query

If you do not control the call to WP_Query – for instance when you are using a plugin which always executes a query – but you do have access to what post IDs are passed to the query, you can use this to .

Let’s say there is a function in a plugin written by someone else (so you should not modify the plugin directly):

<?php

function foo_plugin_get_posts( $query_args ) {
	$posts = get_posts( $query_args );

	/*
	 * Something unique to the plugin happens here to justify
	 * the function's existence.
	 */

	return $posts;
}

In this scenario, you can do this, as described in this post by David Labbe, to make sure no posts are returned:

<?php

$query_args = array(
	'post__in' => array( 0 ),
);

$my_posts = foo_plugin_get_posts( $query_args );

Because array( 0 ) is not an empty array (it has one element: the number zero), it is not “falsey” so WP_Query will take it into account when constructing the SQL query.

Since WordPress enumerates post IDs starting from 1, we are sure that a post with ID 0 does not exist so no posts will actually be retrieved by the WP_Query.

This solution has the overhead of actually running a query but at least we have a workaround that uses WP_Query‘s API.

Takeaways from this issue

  • Be careful when passing falsey values to WordPress functions.
  • Consult the documentation for any potential quirks regarding the parameters you are planning to use. If there is none, sometimes it is good to check the source code.
  • Test your code! 🙂

Further reading

Comments