25

I'm working on a website with a search feature that allows users to search through a lot of post meta. There is a specific search pattern that I would like to forcibly return no results for. The WP_Query technically will find results in the database, but I'd like to override that somehow to force it to return no results to trigger the if( $example->have_posts() ) to fail.

Is there some sort of parameter I can pass to WP_Query like 'force_no_results' => true that will force it to return no results?

  • 1
    IT seems like you're asking for an already determined implementation, rather than asking how to solve the root problem. Reading between the lines, I think what you really should be asking is: how do I make a specific search pattern unqueryable?. Causing WP_Query() to return no results may or may not be the best way to answer that question. It also might be helpful if you would describe the search pattern that you want to be unqueryable. Knowing the search pattern might help ferret out a solution. – Chip Bennett Apr 9 '14 at 14:51
31

Try

'post__in' => array(0)

Simple and to the point.

| improve this answer | |
  • My first thought was — this gotta go wrong somewhere, but from skimming the related code this should actually work rather nicely. :) – Rarst Apr 9 '14 at 14:48
  • 5
    the zero is very important as just an empty array will return the recent posts. – Mark Kaplun Feb 7 '17 at 15:01
  • Thanks! This solved a bug for me as post__in was returning posts when passed an empty array... array(0) works great! This is weird but it can actually be traced to an issue that came up in WP core as a bug but was then left as-is because too many theme/plugin developers built functionality around it -_- core.trac.wordpress.org/ticket/28099 – EranSch Mar 29 '17 at 15:19
3

Curiously there is no clean/explicit way to short circuit WP_Query.

If it's main query you might work something out around WP->parse_request(), there seems to be relatively recent (3.5) do_parse_request filter there.

But for WP_Query itself dirty hacks are usually in order, such as short-circuiting SQL query by adding AND 1=0 via posts_where filter, etc.

| improve this answer | |
  • 2
    Thanks for the info. It was a secondary loop btw. And I just ended up doing a dirty hack like "post_type" => "break_loop" which is a non-existant post type. – somebodysomewhere Apr 9 '14 at 0:59
2

The problems on set a query parameter to unexistent value are 2:

  • The query will run, so even if you already know will be no results there is a little performance price to pay
  • WordPress queries has 19 different 'posts_*' filter hooks ('posts_where', 'post_join', etc..) that act on query, so you can never be sure that even setting unexistent param the query return no results, a simple OR clause returned by a filter make return something.

You need a little bit hardcore routine to be sure a query return no result and there is no (or very minimun) performance issue.

To trigger that routine, you can use every method, technically you can pass any argument to WP_Query, event arguments that doesn't exists.

So if you like something like 'force_no_results' => true, you can use it like so:

$a = new WP_Query( array( 's' => 'foo', 'force_no_results' => true ) );

and add a callback running on 'pre_get_posts' that do the hard work:

add_action( 'pre_get_posts', function( $q ) {
  if (array_key_exists('force_no_results', $q->query) && $q->query['force_no_results']) {
    $q->query = $q->query_vars = array();
    $added = array();
    $filters = array(
      'where', 'where_paged', 'join', 'join_paged', 'groupby', 'orderby', 'distinct',
      'limits', 'fields', 'request', 'clauses', 'where_request', 'groupby_request',
      'join_request', 'orderby_request', 'distinct_request','fields_request',
      'limits_request', 'clauses_request'
    );
    // remove all possible interfering filter and save for later restore
    foreach ( $filters as $f ) {
      if ( isset($GLOBALS['wp_filter']["posts_{$f}"]) ) {
        $added["posts_{$f}"] = $GLOBALS['wp_filter']["posts_{$f}"];
        unset($GLOBALS['wp_filter']["posts_{$f}"]);
      }
    }
    // be sure filters are not suppressed
    $q->set( 'suppress_filters', FALSE );
    $done = 0;
    // use a filter to return a non-sense request
    add_filter('posts_request', function( $r ) use( &$done ) {
      if ( $done === 0 ) { $done = 1;
        $r = "SELECT ID FROM {$GLOBALS['wpdb']->posts} WHERE 0 = 1";
      }
      return $r;
    });
    // restore any filter that was added and we removed
    add_filter('posts_results', function( $posts ) use( &$done, $added ) {
      if ( $done === 1 ) { $done = 2;
        foreach ( $added as $hook => $filters ) {
          $GLOBALS['wp_filter'][$hook] = $filters;
        }
      }
      return $posts;
    });
  }
}, PHP_INT_MAX );

What this code does is run on 'pre_get_posts' as late as possible. If the argument 'force_no_results' is present in the query, then:

  1. first remove all possible filters that can interfere with the query, and store them inside an helper array
  2. after be sure the filter are triggered, adda filter that return this kind of request: SELECT ID FROM wp_posts WHERE 0 = 1 once all filters are removed, there is no possibilities this query is changed and it is very fast, and has no result for sure
  3. immediately after this query is ran, all original filters (if there were any) are restored and all subsequent queries will works as expected.
| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.