web-dev-qa-db-fra.com

Est-il possible d'arrêter complètement la récupération des publications par WP_Query?

J'essaie d'utiliser WP Redis pour mettre en cache l'intégralité de $ wp_query object with key est $ query_vars_hash .

Voici comment $wp_query a été ajouté à $wp_object_cache:

add_action('wp', function($wp)
{
    if ( is_admin() ) return;

    global $wp_query;

    if ( !wp_cache_get($wp_query->query_vars_hash, 'globals') )
    {
        wp_cache_add($wp_query->query_vars_hash, $wp_query, 'globals');
    }
});

Ensuite, je dois vérifier si une requête a déjà été mise en cache avant que WP_Query puisse récupérer les publications:

add_action('pre_get_posts', function($query)
{
    if ( is_admin() ) return;

    $cached_query = wp_cache_get($query->query_vars_hash, 'globals');

    if ($cached_query)
    {
        $GLOBALS['wp_query'] = &$cached_query;

        return; // Return immediately to prevent retrieving posts again.
    }
});

Problème :

return ou exit ne fonctionne pas dans ce cas. Ensuite, WP_Query continuera à utiliser la base de données pour récupérer à nouveau les publications.

Question :

Quel que soit le plugin, est-il possible d'arrêter complètement la récupération de WP_Query?

8
MinhTri

Pour le moment, ce n'est pas possible.

Lorsque 'pre_get_posts' est exécuté, il est trop tard pour arrêter WP_Query afin d'effectuer une requête.

WordPress lui-même, lorsque vous essayez d'interroger une taxonomie qui n'existe pas, ajoute AND (0 = 1) à la clause WHERE de la requête SQL, pour s'assurer qu'il ne renvoie aucun résultat très rapidement ...

Il existe un ticket trac avec un patch qui atterrira probablement dans le noyau avec WP 4.6, qui introduit un nouveau filtre: 'posts_pre_query'. Si vous retournez un tableau sur ce filtre, WP_Query arrête le traitement et utilise le tableau fourni en tant que tableau posts.

Cela pourrait en quelque sorte vous aider à mettre en œuvre ce que vous essayez de faire.

En attendant, tout ce que vous pouvez faire est en quelque sorte hackish , le trick core lui-même est plutôt hack.

Récemment, je commence à utiliser un trick lorsque je veux empêcher WordPress de faire des choses que je ne peux pas arrêter proprement: je lève une exception et je l’attrape pour continuer le flux d’applications.

Je vais vous montrer un exemple. Notez que tout le code ici est complètement non testé.

Tout d'abord, écrivons une exception personnalisée:

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;
   }
}

L'exception est conçue pour agir comme une sorte deDTOpour transporter un objet de requête, de sorte que vous puissiez obtenir et l'utiliser dans un bloc catch.

Mieux expliqué avec le 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());
        }
    }
});

Cela devrait plus ou moins fonctionner, cependant, il y a beaucoup de hooks que vous n'allez pas utiliser, par exemple "the_posts" et bien plus encore ... si vous avez du code qui utilise l'un de ces hooks pour le déclencher, il se cassera.

Vous pouvez utiliser la fonction cached_query_set pour déclencher certains des points d'ancrage nécessaires à votre thème/plug-in.

11
gmazzap

C'est PHP question plus qu'une question WordPress.

Comme @Mark a commenté:

revenant de l'action ne revient pas par magie de la fonction appelante

C'est vrai. Placer return dans function signifie quitter la fonction et placer return dans un fichier PHP signifie quitter le fichier. Ne vous trompez pas avec PHP construct exit(): P (vous trouverez peut-être une meilleure réponse sur SO à propos de PHP return).

Et pour répondre à ta question

Vous pouvez réduire la charge de la requête en récupérant une seule colonne au lieu d'une table complète. J'aime @birgire did here Supprimer la requête de page d'accueil

Peut-être une meilleure réponse à venir. Je viens de partager ce que je sais :)

2
Sumit

Cela sera rendu possible en 4.6 (en supposant qu'il n'y ait pas de changement jusqu'à la sortie) avec le nouveau filtre posts_pre_query/ https://core.trac.wordpress.org/ticket/36687

2
Mark Kaplun

Oui, c'est possible en fonction de ce que vous voulez mettre en cache. J'ai fait la même chose pour cacher la boucle principale sur notre page d'accueil. Vous pouvez utiliser essentiellement les posts_request et posts_results pour pirater la requête et accéder au cache à la place, puis utilisez également found_posts pour corriger la pagination.

Exemple très approximatif tiré de notre code (non testé), mais vous devriez vous aider à vous faire une idée:

<?php
/**
 * Kill the query if we have the result in the cache
 * @var [type]
 */
add_filter( 'posts_request', function( $request, $query ) {
    if ( is_home() && $query->is_main_query() ) {

        $page = ( get_query_var( 'paged' ) ) ? get_query_var( 'paged' ) : 1;

        $key = 'homepage_query_cache_' . $page;

        if ( wp_cache_get( $key, 'cache_group' ) )
            $request = null;

    }

    return $request;
}, 10, 2 );

/**
 * Get the result from the cache and set it as the query result
 * Or add the query result to the cache if it's not there
 * @var [type]
 */
add_filter( 'posts_results', function( $posts, $query ) {

    if ( is_home() && $query->is_main_query() ) {

        $page = ( get_query_var( 'paged' ) ) ? get_query_var( 'paged' ) : 1;

        $key = 'homepage_query_cache_' . $page;

        if ( $cached_posts = wp_cache_get( $key, 'cache_group' ) ) {
            $posts = $cached_posts;
        } else {
            wp_cache_set( $key . '_found_posts', $query->found_posts, 'cache_group', HOUR_IN_SECONDS );
            wp_cache_set( $key, $posts, 'cache_group', HOUR_IN_SECONDS );
        }
    }

    return $posts;

}, 10, 2 );

/**
 * Correct the found posts number if we've hijacked the query results
 * @var [type]
 */
add_filter( 'found_posts', function( $num, $query ) {
    if ( is_home() && $query->is_main_query() ) {
        $page = ( get_query_var( 'paged' ) ) ? get_query_var( 'paged' ) : 1;

        $key = 'homepage_query_cache_' . $page;

        if ( $found_posts = wp_cache_get( $key . '_found_posts', 'cache_group' ) )
            $num = $found_posts;
    }

    return $num;
}, 10, 2 );

Plus ici: https://www.reddit.com/r/Wordpress/comments/19crcn/best_practice_for_hijacking_main_loop_and_caching/

2
OzTheGreat