É possível parar completamente a recuperação de postagens do WP_Query?

8

Estou tentando usar o WP Redis para armazenar em cache todo o objeto $ wp_query com a chave $ query_vars_hash .

Foi assim que $wp_queryfoi adicionado a $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');
    }
});

Em seguida, preciso verificar se uma consulta já foi armazenada em cache antes de WP_Queryrecuperar as postagens:

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

Problema :

returnou exitnão funciona neste caso. Em seguida, WP_Queryainda atingirá o banco de dados para recuperar as postagens novamente.

Pergunta :

Independentemente do plug-in, é possível parar completamente a WP_Queryrecuperação de postagens?

SarahCoding
fonte
Eu sinto que o plugin deve lidar com isso ... Você tem certeza de que está fazendo isso da maneira certa? Você já perguntou sobre isso em seus fóruns? Em seus problemas no github?
Howdy_McGee
@Howdy_McGee, o plug-in usa as mesmas funcionalidades da API de cache padrão do WordPress . A única diferença é que isso ajuda a conectar-se ao servidor Redis. Claro, também estou tentando encontrar o caminho certo.
SarahCoding
Não sei por que você acha que a consulta não será acionada. retornando a partir da ação não retornam por magia da função de chamada
Mark Kaplun
@ MarkKaplun Também duvido disso, mas returnpode ser o único comando que podemos chamar nesse caso.
SarahCoding
@ Dan, eu não entendo o que é que você assume, você obviamente assumir algo que não é verdade, provavelmente no nível do PHP
Mark Kaplun

Respostas:

11

No momento, não é possível.

Quando 'pre_get_posts'executado, é muito tarde para parar WP_Querypara executar uma consulta.

O próprio WordPress, quando você tenta consultar uma taxonomia que não existe, adiciona AND (0 = 1)à WHEREcláusula da consulta SQL, para garantir que ela não retorne resultados muito rapidamente ...

Há um bilhete de trac com um patch que provavelmente irá cair no núcleo com WP 4.6, que introduz um novo filtro: 'posts_pre_query'. O retorno de uma matriz nesse filtro WP_Queryinterromperá o processamento e usará a matriz fornecida como sua matriz de postagens.

De alguma forma, isso pode ajudá-lo a implementar o que você está tentando fazer.

Esperando por isso, qualquer coisa que você possa fazer é de alguma forma burra , o truque que o próprio núcleo usa também é bastante burro.

Recentemente, estou começando a usar um truque quando quero parar o WordPress para fazer coisas que não consigo parar de maneira limpa: lanço uma exceção e a pego para continuar o fluxo do aplicativo.

Eu vou te mostrar um exemplo. Observe que todo o código aqui é completamente não testado.

Primeiro de tudo, vamos escrever uma exceção personalizada:

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

A exceção foi projetada para atuar como uma espécie de DTO para transportar um objeto de consulta, para que em um catchbloco você possa obtê-lo e usá-lo.

Melhor explicado com o código:

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

Isso deve funcionar mais ou menos, no entanto, existem muitos ganchos que você não vai disparar, por exemplo, "the_posts"e muito mais ... se você tiver um código que use um desses ganchos para acioná-lo, ele será quebrado.

Você pode usar a cached_query_setfunção para acionar alguns dos ganchos que seu tema / plug-in pode exigir.

gmazzap
fonte
Por que não funciona com a classe de exceção padrão? Isso me mostra um erro de exceção não capturada?
Sumit
Ele deve funcionar com exceção padrão e uma propriedade pública, mas você precisa capturar a exceção padrão se você jogá-lo @Sumit
gmazzap
Bem, eu fiz isso apenas usando este exemplo. Mas eu recebo um erro de exceção não detectado. Corri o exemplo mais simples de exceção, mas parece que do_actiondeveria estar em trybloco.
Sumit
Abordagem interessante que pode ser aplicada em vários lugares do WordPress, vou lembrá-lo ;-) ps: DTO = Objeto de Transferência de Dados ?
birgire
@birgire yes :)
gmazzap
2

Esta é uma questão de PHP mais do que uma questão de WordPress.

Como o @Mark comentou:

retornando da ação não retorne por mágica da função de chamada

Isso é verdade. Colocar returnna função significa sair da função e colocar retorno em um arquivo PHP significa sair do arquivo. Não se confunda com a construção do PHP exit(): P (Você pode encontrar uma resposta melhor no SO sobre PHP return).

E para responder sua pergunta

Você pode reduzir a carga da consulta buscando uma única coluna em vez da tabela completa. Como @birgire fez aqui Remover a consulta da página inicial

Pode ser uma resposta melhor ainda por vir. Acabei de compartilhar que o que eu sei :)

Sumit
fonte
1
@Dan você recebeu muitos acessos ao banco de dados após neutralizar a solicitação de consulta através do posts_requestfiltro? Com essa abordagem de + coluna única, saímos WP_Querymais cedo do que usando o posts_pre_queryfiltro. Também observe as postagens adesivas com posts_pre_querymas podemos removê-las com $q->set( 'ignore_sticky_posts', 1 );, por exemplo, o exemplo aqui .
birgire
@ Birgire Parece posts_pre_queryque não ajuda. Sua solução é a melhor até agora. :) Se você souber como podemos sair da consulta logo após pre_get_posts, isso pode ser ótimo. Obrigado!
precisa saber é o seguinte
@Dan posts_pre_queryestará disponível a partir de 4.6;)
Sumit
Outra abordagem que vem à mente é tentar estender a WP_Queryclasse com um get_posts()método personalizado , com uma possível existência antecipada e que chama parent::get_posts() e tentar substituir a consulta relevante por ela. Mas eu não sei se isso iria funcionar ou fazer sentido com o seu caso aqui ;-) @ Dan
birgire
1
Talvez um pouco de Aerosmith - Livin' On The Edge poderia ajudar com isso ;-) @ Dan
birgire
2

Isso será possível no 4.6 (assumindo que não há alterações até o lançamento) com o novo posts_pre_queryfiltro https://core.trac.wordpress.org/ticket/36687

Mark Kaplun
fonte
@ Dan, isso é o que acontece quando você deseja concluir o pensamento que você teve antes de ir dormir e não ler as outras respostas em primeiro lugar;)
Mark Kaplun
Bro, é tarde demais agora. Vou ler essas respostas mais tarde ;-)
SarahCoding
2

Sim, é possível, dependendo do que você deseja armazenar em cache. Fiz uma coisa semelhante para armazenar em cache o loop principal em nossa página inicial. Essencialmente, você pode usar as teclas posts_requeste posts_resultspara seqüestrar a consulta e acessar o cache. Em seguida, use também found_postspara corrigir a paginação.

Exemplo realmente grosseiro retirado do nosso código (não testado), mas você deve ajudá-lo a ter a ideia:

<?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 );

Mais aqui: https://www.reddit.com/r/Wordpress/comments/19crcn/best_practice_for_hijacking_main_loop_and_caching/

OzTheGreat
fonte