Wordpress - Is it possible to completely stop WP_Query retrieving posts?

At the moment, it is not possible.

When 'pre_get_posts' runs, is too late to stop WP_Query to perform a query.

WordPress itself, when you try to query a taxonomy that does not exists, adds AND (0 = 1) to the WHERE clause of the SQL query, to ensure it returns no results very quickly...

There's a trac ticket with a patch that will probably lands in core with WP 4.6, that introduces a new filter: 'posts_pre_query'. Returning an array on that filter will make WP_Query stop processing and use the array provided as its posts array.

This could somehow helps you in implementing what you are trying to do.

Waiting fot this, anything you could do is somehow hackish, the trick core itself uses is quite hackish as well.

Recently, I'm starting using a trick when I want to stop WordPress to do things that I can't stop in a clean way: I throw an exception and catch it to continue application flow.

I'll show you an example. Note all the code here is completely untested.

First of all, let's write a custom exception:

class My_StopWpQueryException extends Exception {

   private $query;

   public static forQuery(WP_Query $query) {
     $instance = new static();
     $instance->query = $query;

     return $instance;
   }

   public function wpQuery() {
     return $this->query;
   }
}

The exception is designed to act as a sort of DTO to transport a query object, so that in a catch block you can get and use it.

Better explained with code:

function maybe_cached_query(WP_Query $query) {
    $cached_query = wp_cache_get($query->query_vars_hash, 'globals');
    if ($cached_query instanceof WP_Query)
       throw My_StopWpQueryException::forQuery($cached_query);
}

function cached_query_set(WP_Query $query) {
    $GLOBALS['wp_query'] = $query;
    $GLOBALS['wp_the_query'] = $query;
    // maybe some more fine-tuning here...
}

add_action('pre_get_posts', function(WP_Query $query) {
    if ($query->is_main_query() && ! is_admin()) {
        try {
           maybe_cached_query($query);
        } catch(My_StopWpQueryException $e) {
           cached_query_set($e->wpQuery());
        }
    }
});

This should more or less work, however, there are a lot of hooks that you are not going to fire, for example "the_posts" and much more... if you have code that use one of those hooks to trigger in, it will break.

You can use the cached_query_set function to fire some of the hooks that your theme / plugins may require.


This is PHP question more than a WordPress question.

As @Mark commented:

returning from the action do not return by magic from the calling function

That is true. Placing return in function mean exit the function and placing return in a PHP file mean exit the file. Do not get confused with PHP construct exit() :P (You might find a better answer on SO about PHP return).

And to answer your question

You can reduce the load of query by fetching a single column instead of full table. Like @birgire did here Remove the Homepage Query

May be a better answer yet to come. I just shared that what I know :)


It will be made possible in 4.6 (assuming no changes till release) with the new posts_pre_query filter https://core.trac.wordpress.org/ticket/36687