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

8

J'essaie d'utiliser WP Redis pour mettre en cache tout l' objet $ wp_query avec la clé est $ query_vars_hash .

Voici comment a $wp_queryé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 de WP_Querypouvoir récupérer des messages:

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 :

returnou exitne fonctionne pas dans ce cas. Ensuite, il WP_Queryatteindra toujours la base de données pour récupérer à nouveau les messages.

Question :

Quel que soit le plugin, est-il possible d'arrêter complètement la WP_Queryrécupération des messages?

SarahCoding
la source
Je pense que le plugin devrait gérer cela ... Êtes-vous sûr que vous y parvenez de la bonne façon? Avez-vous posé des questions à ce sujet sur leurs forums? Sur leurs problèmes de github?
Howdy_McGee
@Howdy_McGee le plugin utilise les mêmes fonctionnalités que l' API de mise en cache WordPress par défaut . La seule différence est qu'il permet de se connecter au serveur Redis. Bien sûr, j'essaie aussi de trouver le bon chemin.
SarahCoding
Je ne sais pas pourquoi pensez-vous que la requête ne se déclenchera pas. revenant de l'action ne reviennent pas par magie de la fonction appelante
Mark Kaplun
@MarkKaplun Je double également à ce sujet, mais returnpeut-être la seule commande que nous pouvons appeler dans ce cas.
SarahCoding
@ Dan, je ne comprends pas ce que vous supposez, vous supposez évidemment quelque chose qui n'est pas vrai, probablement au niveau php
Mark Kaplun

Réponses:

11

Pour le moment, ce n'est pas possible.

Lors de l' 'pre_get_posts'exécution, il est trop tard pour s'arrêter WP_Querypour effectuer une requête.

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

Il y a un billet de trac avec un patch qui sera probablement dans les terres avec noyau WP 4.6, qui introduit un nouveau filtre: 'posts_pre_query'. Le renvoi d'un tableau sur ce filtre WP_Queryarrêtera le traitement et utilisera le tableau fourni comme tableau de publications.

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

En attendant, tout ce que vous pourriez faire est en quelque sorte piraté , le truc que le noyau lui-même utilise est également assez piraté.

Récemment, je commence à utiliser une astuce lorsque je veux arrêter WordPress pour faire des choses que je ne peux pas arrêter de manière propre: je lève une exception et la rattrape pour continuer le flux d'application.

Je vais vous montrer un exemple. Notez que tout le code ici n'est pas 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 de DTO pour transporter un objet de requête, de sorte que dans un catchbloc, vous pouvez l'obtenir et l'utiliser.

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 tirer, par exemple "the_posts"et bien plus encore ... si vous avez du code qui utilise l'un de ces hooks pour se déclencher, il se cassera.

Vous pouvez utiliser la cached_query_setfonction pour déclencher certains des crochets dont votre thème / plugins peut avoir besoin.

gmazzap
la source
Pourquoi ne fonctionne pas avec la classe d'exception par défaut? Cela me montre une erreur d'exception non capturée?
Sumit
Cela devrait fonctionner avec une exception standard et une propriété publique, mais vous devez intercepter l'exception standard si vous la lancez @Sumit
gmazzap
Eh bien, je l'ai fait simplement en utilisant cet exemple. Mais je reçois une erreur d'exception non capturée. J'ai exécuté l'exemple d'exception le plus simple, mais il semble qu'il do_actiondevrait être en trybloc.
Sumit
Approche intéressante qui peut être appliquée à divers endroits dans WordPress, je garderai cela à l'esprit ;-) ps: DTO = Data Transfer Object ?
birgire
@birgire yes :)
gmazzap
2

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

Comme l'a commenté @Mark :

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

C'est vrai. Placer returndans la fonction signifie quitter la fonction et placer return dans un fichier PHP signifie quitter le fichier. Ne vous confondez pas avec la construction PHP exit(): P (vous pourriez trouver une meilleure réponse sur SO à propos de PHP return).

Et pour répondre à votre question

Vous pouvez réduire la charge de requête en récupérant une seule colonne au lieu d'une table complète. Comme @birgire l'a fait ici Supprimer la requête de la page d'accueil

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

Sumit
la source
1
@Dan avez-vous obtenu de nombreux accès à la base de données après avoir neutralisé la requête via le posts_requestfiltre? Avec cette approche + colonne unique, nous quittons WP_Queryplus tôt que l'utilisation du posts_pre_queryfiltre .. Faites également attention aux messages collants avec posts_pre_querymais nous pouvons le supprimer avec $q->set( 'ignore_sticky_posts', 1 );par exemple l'exemple ici .
birgire
@birgire Look like posts_pre_queryn'aide pas. Votre solution est la meilleure jusqu'à présent. :) Si vous savez comment nous pouvons quitter la requête juste après pre_get_posts, cela pourrait être parfait. Je vous remercie!
SarahCoding
@Dan posts_pre_querysera disponible à partir du 4.6;)
Sumit
Une autre approche qui me vient à l'esprit est d'essayer d'étendre la WP_Queryclasse avec une get_posts()méthode personnalisée , avec un possible début précoce et qui appelle parent::get_posts() et tente de remplacer la requête pertinente avec elle. Mais je ne sais pas si cela fonctionnerait ou aurait un sens avec votre cas ici ;-) @Dan
birgire
1
Peut-être qu'un peu d' Aerosmith - Livin 'On The Edge pourrait vous aider ;-) @Dan
birgire
2

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

Mark Kaplun
la source
@Dan, c'est ce qui se passe lorsque vous voulez compléter la pensée que vous aviez avant de vous coucher et de ne pas lire les autres réponses en premier;)
Mark Kaplun
Bro, c'est trop tard maintenant. Je lirai ces réponses plus tard ;-)
SarahCoding
2

Oui, c'est possible selon ce que vous voulez mettre en cache. J'ai fait une chose similaire pour mettre en cache la boucle principale sur notre page d'accueil. Essentiellement, vous pouvez utiliser posts_requestet posts_resultspour détourner la requête et accéder au cache à la place, puis également utiliser found_postspour corriger la pagination.

Exemple vraiment grossier tiré de notre code (non testé) mais vous devriez vous aider à avoir l'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/

OzTheGreat
la source