¿Es posible detener completamente la recuperación de publicaciones de WP_Query?

8

Estoy tratando de usar WP Redis para almacenar en caché todo el objeto $ wp_query con la clave $ query_vars_hash .

Así es como $wp_queryse agregó 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');
    }
});

Luego, debo verificar si una consulta ya se ha almacenado en caché antes de WP_Querypoder recuperar publicaciones:

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 :

returno exitno funciona en este caso. Entonces, WP_Querytodavía llegará a la base de datos para recuperar publicaciones nuevamente.

Pregunta :

Independientemente del complemento, ¿es posible dejar de WP_Queryrecuperar publicaciones por completo ?

SarahCoding
fuente
Siento que el complemento debería manejar esto ... ¿Estás seguro de que vas a hacer esto de la manera correcta? ¿Has preguntado sobre esto en sus foros? ¿Sobre sus problemas con Github?
Howdy_McGee
@Howdy_McGee, el complemento utiliza las mismas funcionalidades que la API predeterminada de almacenamiento en caché de WordPress . La única diferencia es que ayuda a conectarse al servidor Redis. Por supuesto, también estoy tratando de encontrar el camino correcto.
SarahCoding
No estoy seguro de por qué crees que la consulta no se activará. regresando de la acción no regreses por magia desde la función de llamada
Mark Kaplun
@ MarkKaplun También doblo sobre eso, pero returnpodría ser el único comando que podemos llamar en este caso.
SarahCoding
@Dan, no entiendo qué es lo que asumes, obviamente asumes algo que no es cierto, probablemente a nivel de php
Mark Kaplun

Respuestas:

11

Por el momento, no es posible.

Cuando se 'pre_get_posts'ejecuta, es demasiado tarde para detenerse WP_Querypara realizar una consulta.

WordPress en sí mismo, cuando intenta consultar una taxonomía que no existe, agrega AND (0 = 1)a la WHEREcláusula de la consulta SQL, para asegurarse de que no devuelve resultados muy rápidamente ...

Hay un billete trac con un parche que probablemente lo hará en tierras núcleo con WP 4.6, que introduce un nuevo filtro: 'posts_pre_query'. Si devuelve una matriz en ese filtro, se WP_Querydetendrá el procesamiento y se usará la matriz proporcionada como matriz de publicaciones.

De alguna manera, esto podría ayudarlo a implementar lo que está tratando de hacer.

A la espera de esto, cualquier cosa que puedas hacer es de alguna manera hack , el núcleo del truco en sí mismo también es bastante hack.

Recientemente, estoy comenzando a usar un truco cuando quiero detener WordPress para hacer cosas que no puedo detener de una manera limpia: lanzo una excepción y la atrapo para continuar el flujo de la aplicación.

Te mostraré un ejemplo. Tenga en cuenta que todo el código aquí no se ha probado por completo.

Antes que nada, escribamos una excepción 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;
   }
}

La excepción está diseñada para actuar como una especie de DTO para transportar un objeto de consulta, de modo que en un catchbloque pueda obtenerlo y usarlo.

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

Esto debería funcionar más o menos, sin embargo, hay muchos ganchos que no vas a disparar, por ejemplo, "the_posts"y mucho más ... si tienes un código que usa uno de esos ganchos para disparar, se romperá.

Puede usar la cached_query_setfunción para activar algunos de los ganchos que su tema / complementos pueden requerir.

gmazzap
fuente
¿Por qué no funciona con la clase de excepción predeterminada? ¿Me muestra un error de excepción no detectada?
Sumit
Debería funcionar con una excepción estándar y una propiedad pública, pero debe detectar la excepción estándar si la arroja @Sumit
gmazzap
Bueno, lo hice simplemente usando este ejemplo. Pero recibo un error de excepción no detectado. Ejecuté el ejemplo más simple de excepción, pero parece que do_actiondebería estar en trybloque.
Sumit
Enfoque interesante que se puede aplicar en varios lugares en WordPress, lo tendré en cuenta ;-) ps: DTO = ¿ Objeto de transferencia de datos ?
Birgire
@birgire sí :)
gmazzap
2

Esta es una pregunta PHP más que una pregunta de WordPress.

Como @Mark comentó:

regresando de la acción no regreses por magia desde la función de llamada

Eso es verdad. Colocar returnen la función significa salir de la función y colocar el retorno en un archivo PHP significa salir del archivo. No se confunda con PHP construct exit(): P (es posible que encuentre una mejor respuesta en SO sobre PHP return).

Y para responder a tu pregunta

Puede reducir la carga de consultas obteniendo una sola columna en lugar de una tabla completa. Como hizo @birgire aquí Eliminar la consulta de la página de inicio

Puede ser una mejor respuesta por venir. Acabo de compartir que lo que sé :)

Sumit
fuente
1
@ ¿Recibió muchas visitas a la base de datos después de neutralizar la solicitud de consulta a través del posts_requestfiltro? Con ese enfoque de + columna simple, salimos WP_Queryantes de usar el posts_pre_queryfiltro. También tenga cuidado con las publicaciones adhesivas con, posts_pre_querypero podemos eliminarlo, por $q->set( 'ignore_sticky_posts', 1 );ejemplo, en el ejemplo aquí .
Birgire
@birgire Parece posts_pre_queryque no ayuda. Tu solución es la mejor hasta ahora. :) Si sabes cómo podemos salir de la consulta inmediatamente después pre_get_posts, eso podría ser genial. ¡Gracias!
SarahCoding
@Dan posts_pre_queryestará disponible desde 4.6;)
Sumit
Otro enfoque que viene a la mente es intentar extender la WP_Queryclase con un get_posts()método personalizado , con una posible existencia temprana y que llama parent::get_posts() e intenta anular la consulta relevante con él. Pero no sé si eso funcionaría o tendría sentido con su caso aquí ;-) @Dan
birgire
1
Tal vez un poco de Aerosmith: Livin 'On The Edge podría ayudar con eso ;-) @Dan
birgire
2

Será posible en 4.6 (suponiendo que no haya cambios hasta el lanzamiento) con el nuevo posts_pre_queryfiltro https://core.trac.wordpress.org/ticket/36687

Mark Kaplun
fuente
@ Dan, esto es lo que sucede cuando quieres completar el pensamiento que tenías antes de irte a dormir y no leer las otras respuestas primero;)
Mark Kaplun
Hermano, ya es demasiado tarde. Leeré esas respuestas más tarde ;-)
SarahCoding
2

Sí, es posible dependiendo de lo que quieras almacenar en caché. He hecho algo similar para almacenar en caché el bucle principal en nuestra página de inicio. Esencialmente, puede usar el posts_requesty posts_resultspara secuestrar la consulta y presionar el caché en su lugar, luego también usar found_postspara corregir la paginación.

Ejemplo realmente tosco extraído de nuestro código (no probado) pero debería ayudarlo a tener la idea:

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

Más aquí: https://www.reddit.com/r/Wordpress/comments/19crcn/best_practice_for_hijacking_main_loop_and_caching/

OzTheGreat
fuente