replace the WP_Query class in the main query

For a project I needed to heavily modify the behavior of WP_Query. So what I did was extending WP_Query basically like this:

class eacf_shared_query extends \WP_Query {

    function query( $query ) {

        // do things before ...

        $posts = parent::query($query);

        // do things after ...

        return $posts;
    }

This works like a charm for all my custom queries for which I now use my extended class, but I’m unable to find a way to stuff this into the main_query.

So my question is:
Is there any known way to replace/extend the WP_Query class in the main_query?


I did find the above to be the only solution and didn’t get any further with using the filters supplied within the original class cause:

  • There seems to be no filter pair that is guaranteed to be run before
    and after the query so I can’t safely achieve a construct like in the example above.
  • My “do things after” logic contains nested calls to WP_Query() which too easily end up in a query-loop if I use filters.
  • The SQL filters seem to be not powerfull enough to create a single query fetching all of the posts (like building a UNION query). I’m in the need of merging posts from different blogs in one query and it seems this can’t be achieved with filters like posts_clauses_request or query.

2 Answers
2

Ok so it’s doable but could potentially mess up a few things down the line so it’s at your own risk, but then so is everything 🙂

I’ve been trying to do this in order to use a GeoQuery extension class to order events by distance from some location.

The simplest way to do it is this:

add_action( 'after_setup_theme', 'alt_query_class', 1, 0 );
function alt_query_class() {
    if ( ! is_admin() && $GLOBALS[ 'wp_query' ] instanceof WP_Query ) {
         $GLOBALS[ 'wp_the_query' ] = new WP_Query_Extended();
         $GLOBALS[ 'wp_query' ] = $GLOBALS[ 'wp_the_query' ];
    }
}

In your extended query class you would just do what you have above but if your added functionality is something that’s not desirable for every query run then you’d have to do some setup/teardown logic because we’re replacing every instance of WP_Query with the new one.

In my case I used the pre_get_posts filter with a late priority to check whether I needed to add my functionality or not after all the query manipulations were done.

class WP_Query_Extended extends WP_Query {

    function query( $args = array() ) {

        // setup functions
        add_filter( 'pre_get_posts', array( $this, 'check_setup_needed' ), 1000 );

        // run the query
        parent::query( $args );

        // tear down functions
        $this->teardown();
    }

    function check_setup_needed( $query ) {

        // test setup criteria
        if ( $query->get( 'somevar' ) ) {
            $this->setup();
        }

        return $query;
    }

    function setup() {
        // add any filters etc... here
    }

    function teardown() {
        // remove filters etc... here
        remove_filter( 'pre_get_posts', array( $this, 'check_setup_needed' ) );
    }

}

UPDATE:

There’s a big gotcha with this – if someone uses query_posts() eg. could be in a plugin or if you’re writing a plugin then maybe a theme then that function resets the $wp_query global back to a WP_Query instance and there’s no filter with which to change it again afterwards.

If you actually need to do a query_posts() (you shouldn’t!) you can do $wp_query->query( $args ) instead.

Leave a Comment