WP_Query object: what’s the difference between [query] and [query_vars]?

Those [query] and [query_vars], i.e. WP_Query::$query and WP_Query::$query_vars, are defined as: (see source on Trac)

class WP_Query {

  /**
   * Query vars set by the user
   *
   * @since 1.5.0
   * @var array
   */
  public $query;

  /**
   * Query vars, after parsing
   *
   * @since 1.5.0
   * @var array
   */
  public $query_vars = array();

So the [query] is an array of arguments that are passed to the WP_Query class (the $args array in the following example), whereas the [query_vars] is an array of arguments in the [query] array and the default arguments like the ones listed here and including custom arguments that were not originally set in the [query], but are later set using hooks such as pre_get_posts.

So for example, with this:

$args = array(
    'posts_per_page' => 20,
    'post__not_in'   => [ 12419 ],
    'custom_arg'     => true,
);

$query = new WP_Query( $args );
  • var_dump( $query->query ); would output only the items in the above array:

    array(3) {
      ["posts_per_page"]=>
      int(20)
      ["post__not_in"]=>
      array(1) {
        [0]=>
        int(12419)
      }
      ["custom_arg"]=>
      bool(true)
    }
    
  • var_dump( $query->query_vars ); would also output the same items, but with many other items (note the array(64) below) like error and m:

    array(64) {
      ["posts_per_page"]=>
      int(20)
      ["post__not_in"]=>
      array(1) {
        [0]=>
        int(12419)
      }
      ["custom_arg"]=>
      bool(true)
      ["error"]=>
      string(0) ""
      ["m"]=>
      string(0) ""
      ["p"]=>
      int(0)
      ...
    }
    

Now, if I used pre_get_posts to change the post__not_in argument and also to add another custom argument:

add_action( 'pre_get_posts', function ( $query ) {
    if ( $query->get( 'custom_arg' ) ) {
        $query->set( 'post__not_in', [] );

        $query->set( 'custom_arg2', 'foo bar baz' );
    }
} );
  • var_dump( $query->query ); would show no changes, but var_dump( $query->query_vars ); would now output:

    array(65) {
      ["posts_per_page"]=>
      int(20)
      ["post__not_in"]=>
      array(0) {
      }
      ["custom_arg"]=>
      bool(true)
      ["error"]=>
      string(0) ""
      ["m"]=>
      string(0) ""
      ["p"]=>
      int(0)
      ...
      ["custom_arg2"]=>
      string(11) "foo bar baz"
      ...
    }
    
  • And if I apply array_filter() on the query_vars array, i.e. var_dump( array_filter( $query->query_vars ) ), then the output would no longer contain the post__not_in:

    array(9) {
      ["posts_per_page"]=>
      int(20)
      ["custom_arg"]=>
      bool(true)
      ["custom_arg2"]=>
      string(11) "foo bar baz"
      ["cache_results"]=>
      bool(true)
      ["update_post_term_cache"]=>
      bool(true)
      ["lazy_load_term_meta"]=>
      bool(true)
      ["update_post_meta_cache"]=>
      bool(true)
      ["comments_per_page"]=>
      string(2) "50"
      ["order"]=>
      string(4) "DESC"
    }
    

So I don’t know how did you debug the query in question, i.e. I don’t know what code or plugin you used, but I presumed that the [query_vars] values were filtered (empty ones were removed) (by your debug code?) and secondly, the post__not_in argument is probably being filtered similar or the same way I did it above.

Therefore if you can, try suppressing filters by adding 'suppress_filters' => true to the query arguments, and see if the post__not_in array is now good or that the post 12419 is now being excluded from the results.