Cuándo usar WP_query (), query_posts () y pre_get_posts

159

Leí ayer la consulta No sabes de Nacin @ y me enviaron un poco por una madriguera de conejos. Antes de ayer, estaba usando (erróneamente) para todas mis necesidades de consulta. Ahora soy un poco más sabio sobre el uso , pero todavía tengo algunas áreas grises.query_posts()WP_Query()

Lo que creo que sé con certeza:

Si estoy haciendo bucles adicionales en cualquier lugar de una página, en la barra lateral, en un pie de página, cualquier tipo de "publicaciones relacionadas", etc., quiero usar WP_Query(). Puedo usar eso repetidamente en una sola página sin ningún daño. (¿derecho?).

Lo que no sé con certeza

  1. ¿Cuándo uso @ nacin's pre_get_posts vs. WP_Query()? ¿Debo usar pre_get_postspara todo ahora?
  2. Cuando quiero modificar el bucle en una página de plantilla, digamos que quiero modificar una página de archivo de taxonomía, ¿elimino la if have_posts : while have_posts : the_postparte y escribo la mía WP_Query()? ¿O modifico la salida usando pre_get_postsmi archivo functions.php?

tl; dr

Las reglas de tl; dr que me gustaría sacar de esto son:

  1. Nunca query_postsmás uso
  2. Cuando ejecute múltiples consultas en una sola página, use WP_Query()
  3. Al modificar un bucle, haga esto __________________.

Gracias por cualquier sabiduría

Terry

ps: He visto y leído: ¿ Cuándo deberías usar WP_Query vs query_posts () vs get_posts ()? Lo que agrega otra dimensión - get_posts. Pero no trata pre_get_postsen absoluto.

bacalao salado
fuente
@saltcod, ahora es diferente, WordPress evolucionó, agregué algunos comentarios en comparación con la respuesta aceptada aquí .
prosti

Respuestas:

145

Tienes razón al decir:

Nunca query_postsmás uso

pre_get_posts

pre_get_postses un filtro, para alterar cualquier consulta. Se usa con mayor frecuencia para alterar solo la 'consulta principal':

add_action('pre_get_posts','wpse50761_alter_query');
function wpse50761_alter_query($query){

      if( $query->is_main_query() ){
        //Do something to main query
      }
}

(También verificaría que is_admin()devuelva falso , aunque esto puede ser redundante). La consulta principal aparece en sus plantillas como:

if( have_posts() ):
    while( have_posts() ): the_post();
       //The loop
    endwhile;
endif;

Si alguna vez siente la necesidad de editar este bucle, úselo pre_get_posts. es decir, si tiene la tentación de usar query_posts(), use pre_get_postsen su lugar.

WP_Query

La consulta principal es una instancia importante de a WP_Query object. WordPress lo usa para decidir qué plantilla usar, por ejemplo, y cualquier argumento pasado a la url (por ejemplo, paginación) se canaliza a esa instancia del WP_Queryobjeto.

Para los bucles secundarios (por ejemplo, en barras laterales o listas de 'publicaciones relacionadas'), querrá crear su propia instancia separada del WP_Queryobjeto. P.ej

$my_secondary_loop = new WP_Query(...);
if( $my_secondary_loop->have_posts() ):
    while( $my_secondary_loop->have_posts() ): $my_secondary_loop->the_post();
       //The secondary loop
    endwhile;
endif;
wp_reset_postdata();

Aviso wp_reset_postdata();: esto se debe a que el bucle secundario anulará la $postvariable global que identifica la 'publicación actual'. Esto esencialmente restablece eso a lo $postque estamos.

get_posts ()

Esto es esencialmente un contenedor para una instancia separada de un WP_Queryobjeto. Esto devuelve una matriz de objetos de publicación. Los métodos utilizados en el ciclo anterior ya no están disponibles para usted. Esto no es un 'Loop', simplemente una matriz de objetos de publicación.

<ul>
<?php
global $post;
$args = array( 'numberposts' => 5, 'offset'=> 1, 'category' => 1 );
$myposts = get_posts( $args );
foreach( $myposts as $post ) :  setup_postdata($post); ?>
    <li><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></li>
<?php endforeach; wp_reset_postdata(); ?>
</ul>

En respuesta a sus preguntas

  1. Use pre_get_postspara alterar su consulta principal. Use un WP_Queryobjeto separado (método 2) para bucles secundarios en las páginas de la plantilla.
  2. Si desea modificar la consulta del bucle principal, use pre_get_posts.
Stephen Harris
fuente
Entonces, ¿hay algún escenario en el que uno iría directamente a get_posts () en lugar de WP_Query?
urok93
@drtanz: sí. Digamos, por ejemplo, que no necesita paginación o publicaciones fijas en la parte superior; en estos casos, get_posts()es más eficiente.
Stephen Harris el
¿Pero eso no agregaría una consulta adicional donde podríamos modificar pre_get_posts para modificar la consulta principal?
urok93
@drtanz: no lo usaría get_posts()para la consulta principal, es para consultas secundarias.
Stephen Harris el
1
@StephenHarris Right =) Si usa next_post () en el objeto en lugar de usar the_post, no pisa la consulta global y no necesita recordar usar wp_reset_postdata después.
Privateer
55

Hay dos contextos diferentes para los bucles:

  • bucle principal que ocurre según la solicitud de URL y se procesa antes de cargar las plantillas
  • bucles secundarios que ocurren de cualquier otra manera, llamados desde archivos de plantilla o de otra manera

El problema query_posts()es que es un bucle secundario que intenta ser el principal y falla miserablemente. Por lo tanto, olvide que existe.

Para modificar el bucle principal

  • no use query_posts()
  • usar pre_get_postsfiltro con $query->is_main_query()cheque
  • use alternativamente el requestfiltro (un poco demasiado rugoso, por lo que lo anterior es mejor)

Para ejecutar el bucle secundario

Use new WP_Queryo get_posts()que sean bastante intercambiables (este último es un envoltorio delgado para el anterior).

Limpiar

Úselo wp_reset_query()si utilizó query_posts()o se metió con global $wp_querydirectamente, por lo que casi nunca lo necesitará.

Úselo wp_reset_postdata()si utilizó the_post()o se setup_postdata()metió con global $posty necesita restaurar el estado inicial de las cosas relacionadas con la publicación.

Rarst
fuente
3
Rarst quiso decirwp_reset_postdata()
Gregory
23

Hay escenarios legítimos para usar query_posts($query), por ejemplo:

  1. Desea mostrar una lista de publicaciones o publicaciones de tipo de publicación personalizada en una página (usando una plantilla de página)

  2. Desea hacer que la paginación de esas publicaciones funcione

Ahora, ¿por qué querría mostrarlo en una página en lugar de usar una plantilla de archivo?

  1. Es más intuitivo para un administrador (¿su cliente?): Pueden ver la página en 'Páginas'

  2. Es mejor agregarlo a los menús (sin la página, tendrían que agregar la URL directamente)

  3. Si desea mostrar contenido adicional (texto, miniatura de publicación o cualquier meta contenido personalizado) en la plantilla, puede obtenerlo fácilmente desde la página (y todo tiene más sentido para el cliente también). Vea si utilizó una plantilla de archivo, ya sea que necesita codificar el contenido adicional o utilizar, por ejemplo, opciones de tema / complemento (lo que lo hace menos intuitivo para el cliente)

Aquí hay un código de ejemplo simplificado (que estaría en la plantilla de su página, por ejemplo, page-page-of-posts.php):

/**
 * Template Name: Page of Posts
 */

while(have_posts()) { // original main loop - page content
  the_post();
  the_title(); // title of the page
  the_content(); // content of the page
  // etc...
}

// now we display list of our custom-post-type posts

// first obtain pagination parametres
$paged = 1;
if(get_query_var('paged')) {
  $paged = get_query_var('paged');
} elseif(get_query_var('page')) {
  $paged = get_query_var('page');
}

// query posts and replace the main query (page) with this one (so the pagination works)
query_posts(array('post_type' => 'my_post_type', 'post_status' => 'publish', 'paged' => $paged));

// pagination
next_posts_link();
previous_posts_link();

// loop
while(have_posts()) {
  the_post();
  the_title(); // your custom-post-type post's title
  the_content(); // // your custom-post-type post's content
}

wp_reset_query(); // sets the main query (global $wp_query) to the original page query (it obtains it from global $wp_the_query variable) and resets the post data

// So, now we can display the page-related content again (if we wish so)
while(have_posts()) { // original main loop - page content
  the_post();
  the_title(); // title of the page
  the_content(); // content of the page
  // etc...
}

Ahora, para ser perfectamente claros, podríamos evitar usar query_posts()aquí también y usar WP_Queryen su lugar, así:

// ...

global $wp_query;
$wp_query = new WP_Query(array('your query vars here')); // sets the new custom query as a main query

// your custom-post-type loop here

wp_reset_query();

// ...

Pero, ¿por qué haríamos eso cuando tenemos una pequeña función tan agradable disponible?

Lukas Pecinka
fuente
1
Brian, gracias por eso. He estado luchando para que pre_get_posts funcione en una página EXACTAMENTE en el escenario que usted describe: el cliente necesita agregar campos / contenido personalizados a lo que de otro modo sería una página de archivo, por lo que se debe crear una "página"; el cliente necesita ver algo que agregar al menú de navegación, ya que agregar un enlace personalizado se les escapa; etc. +1 de mi parte!
Will Lanni
2
Eso también se puede hacer usando "pre_get_posts". Hice eso para tener una "portada estática" que enumerara mis tipos de publicaciones personalizadas en un orden personalizado y con un filtro personalizado. Esta página también está paginada. Echa un vistazo a esta pregunta para ver cómo funciona: wordpress.stackexchange.com/questions/30851/… En resumen, todavía no hay más escenario legítimo para usar query_posts;)
2ndkauboy
1
Porque "Cabe señalar que usar esto para reemplazar la consulta principal en una página puede aumentar los tiempos de carga de la página, en el peor de los casos, más que duplicar la cantidad de trabajo necesaria o más. Si bien es fácil de usar, la función también es propensa a confusión y problemas más adelante ". Fuente codex.wordpress.org/Function_Reference/query_posts
Claudiu Creanga
Esta respuesta es todo tipo de error. Puede crear una "Página" en WP con la misma URL que el tipo de publicación personalizada. Por ejemplo, si su CPT es Bananas, puede obtener una página llamada Bananas con la misma URL. Entonces terminarías con siteurl.com/bananas. Siempre que tenga archive-bananas.php en su carpeta de temas, utilizará la plantilla y "anulará" esa página. Como se indicó en uno de los otros comentarios, el uso de este "método" crea el doble de carga de trabajo para WP, por lo tanto, NO se debe usar nunca.
Hybrid Web Dev
8

Modifico la consulta de WordPress desde functions.php:

//unfortunately, "IS_PAGE" condition doesn't work in pre_get_posts (it's WORDPRESS behaviour)
//so you can use `add_filter('posts_where', ....);`    OR   modify  "PAGE" query directly into template file

add_action( 'pre_get_posts', 'myFunction' );
function myFunction($query) {
    if ( ! is_admin() && $query->is_main_query() )  {
        if (  $query->is_category ) {
            $query->set( 'post_type', array( 'post', 'page', 'my_postType' ) );
            add_filter( 'posts_where' , 'MyFilterFunction_1' ) && $GLOBALS['call_ok']=1; 
        }
    }
}
function MyFilterFunction_1($where) {
   return (empty($GLOBALS['call_ok']) || !($GLOBALS['call_ok']=false)  ? $where :  $where . " AND ({$GLOBALS['wpdb']->posts}.post_name NOT LIKE 'Journal%')"; 
}
T.Todua
fuente
estaría interesado en ver este ejemplo pero donde la cláusula está en meta personalizada.
Andrew Welch
6

Solo para describir algunas mejoras en la respuesta aceptada, ya que WordPress evolucionó con el tiempo y algunas cosas son diferentes ahora (cinco años después):

pre_get_postses un filtro, para alterar cualquier consulta. Se usa con mayor frecuencia para alterar solo la 'consulta principal':

En realidad es un gancho de acción. No es un filtro, y afectará cualquier consulta.

La consulta principal aparece en sus plantillas como:

if( have_posts() ):
    while( have_posts() ): the_post();
       //The loop
    endwhile;
endif;

En realidad, esto tampoco es cierto. La función have_postsitera el global $wp_queryobjeto que no está relacionado solo con la consulta principal. global $wp_query;puede ser alterado con las consultas secundarias también.

function have_posts() {
    global $wp_query;
    return $wp_query->have_posts();
}

get_posts ()

Esto es esencialmente un contenedor para una instancia separada de un objeto WP_Query.

En realidad, hoy en día WP_Queryes una clase, por lo que tenemos una instancia de una clase.


Para concluir: en el momento @StephenHarris escribió muy probablemente todo esto era cierto, pero con el tiempo las cosas en WordPress han cambiado.

prosti
fuente
Técnicamente, todos los filtros están bajo el capó, las acciones son solo un filtro simple. Pero tiene razón aquí, es una acción que pasa un argumento por referencia, que es la forma en que difiere de las acciones más simples.
Milo
get_postsdevuelve una matriz de objetos de publicación, no un WP_Queryobjeto, por lo que de hecho sigue siendo correcto. y WP_Querysiempre ha sido una clase, instancia de una clase = objeto.
Milo
Gracias, @Milo, correcto por alguna razón tenía un modelo demasiado simplificado en mi cabeza.
prosti