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

1 may 2012, 16:04:08
Vistas: 201K
Votos: 179

Ayer leí @nacin's You don't know Query y me sumergí en un agujero de consultas. Antes de ayer, estaba (equivocadamente) usando query_posts() para todas mis necesidades de consultas. Ahora estoy un poco más informado sobre el uso de WP_Query(), pero todavía tengo algunas áreas grises.

Lo que creo saber con seguridad:

Si estoy creando bucles adicionales en cualquier parte de una página—en la barra lateral, en el pie de página, cualquier tipo de "entradas relacionadas", etc—debo usar WP_Query(). Puedo usarlo repetidamente en una sola página sin ningún problema. (¿correcto?).

Lo que no sé con seguridad

  1. ¿Cuándo debo usar @nacin's pre_get_posts en lugar de WP_Query()? ¿Debería usar pre_get_posts para todo ahora?
  2. Cuando quiero modificar el bucle en una plantilla — digamos que quiero modificar una página de archivo de taxonomía — ¿debo eliminar la parte if have_posts : while have_posts : the_post y escribir mi propio WP_Query()? ¿O debo modificar la salida usando pre_get_posts en mi archivo functions.php?

tl;dr

Las reglas resumidas que me gustaría extraer de esto son:

  1. No usar nunca más query_posts
  2. Cuando se ejecuten múltiples consultas en una sola página, usar WP_Query()
  3. Cuando se modifique un bucle, hacer esto __________________.

Gracias por cualquier sabiduría

Terry

PD: He visto y leído: ¿Cuándo deberías usar WP_Query vs query_posts() vs get_posts()? Lo cual añade otra dimensión — get_posts. Pero no trata en absoluto sobre pre_get_posts.

2
Comentarios

Posible duplicado de ¿Cuándo deberías usar WP_Query vs query_posts() vs get_posts()?

dotancohen dotancohen
3 ene 2016 12:29:13

@saltcod, ahora es diferente, WordPress ha evolucionado, agregué algunos comentarios en comparación con la respuesta aceptada aquí.

prosti prosti
28 dic 2016 03:46:02
Todas las respuestas a la pregunta 5
13
164

Tienes razón al decir:

Nunca uses query_posts nunca más

pre_get_posts

pre_get_posts es un filtro para modificar cualquier consulta. Se usa con más 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() ){
        //Hacer algo con la consulta principal
      }
}

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

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

Si alguna vez sientes la necesidad de editar este loop, usa pre_get_posts. Es decir, si tienes la tentación de usar query_posts(), usa pre_get_posts en su lugar.

WP_Query

La consulta principal es una instancia importante de un objeto WP_Query. WordPress lo usa para decidir qué plantilla utilizar, por ejemplo, y cualquier argumento pasado a la URL (como la paginación) se canaliza en esa instancia del objeto WP_Query.

Para loops secundarios (por ejemplo, en barras laterales o listas de 'publicaciones relacionadas'), querrás crear tu propia instancia separada del objeto WP_Query. Ejemplo:

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

Observa wp_reset_postdata();: esto se debe a que el loop secundario sobrescribirá la variable global $post, que identifica la 'publicación actual'. Esto esencialmente restablece eso al $post en el que estamos.

get_posts()

Esto es esencialmente un envoltorio para una instancia separada de un objeto WP_Query. Devuelve un array de objetos de publicación. Los métodos utilizados en el loop anterior ya no están disponibles para ti. Esto no es un 'Loop', simplemente un array 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 tus preguntas

  1. Usa pre_get_posts para modificar tu consulta principal. Usa un objeto WP_Query separado (método 2) para loops secundarios en las páginas de plantilla.
  2. Si deseas modificar la consulta del loop principal, usa pre_get_posts.
1 may 2012 16:27:31
Comentarios

¿Existe algún escenario en el que uno iría directamente a get_posts() en lugar de WP_Query?

urok93 urok93
25 ago 2012 19:09:40

@drtanz - sí. Por ejemplo, si no necesitas paginación o posts fijos en la parte superior - en estos casos get_posts() es más eficiente.

Stephen Harris Stephen Harris
25 ago 2012 21:00:48

¿Pero no añadiría eso una consulta adicional cuando podríamos simplemente modificar pre_get_posts para alterar la consulta principal?

urok93 urok93
26 ago 2012 23:54:06

@drtanz - no estarías usando get_posts() para la consulta principal - es para consultas secundarias.

Stephen Harris Stephen Harris
27 ago 2012 01:42:19

En tu ejemplo de WP_Query, si cambias $my_secondary_loop->the_post(); por $my_post = $my_secondary_loop->next_post(); puedes evitar tener que recordar usar wp_reset_postdata() siempre que uses $my_post para hacer lo que necesites hacer.

Privateer Privateer
19 sept 2015 01:02:58

@Privateer No es así, WP_Query::get_posts() establece global $post;

Stephen Harris Stephen Harris
19 sept 2015 11:48:05

@StephenHarris Acabo de revisar la clase de consulta y no la veo. Lo verifiqué principalmente porque nunca uso wp_reset_postdata porque siempre hago las consultas de esta manera. Estás creando un nuevo objeto y todos los resultados están contenidos dentro de él.

Privateer Privateer
19 sept 2015 16:14:15

@Privateer - perdón, error tipográfico, WP_Query::the_post(), ver: https://github.com/WordPress/WordPress/blob/759f3d894ce7d364cf8bfc755e483ac2a6d85653/wp-includes/query.php#L3732

Stephen Harris Stephen Harris
19 sept 2015 17:39:19

@StephenHarris Correcto =) Si usas next_post() en el objeto en lugar de usar the_post, no afectas la consulta global y no necesitas recordar usar wp_reset_postdata después.

Privateer Privateer
19 sept 2015 21:01:23

@Privateer Ah, mis disculpas, parece que me confundí. Tienes razón (pero no podrías usar funciones que hagan referencia al global $post como the_title(), the_content()).

Stephen Harris Stephen Harris
23 sept 2015 18:31:04

Cierto =) Yo nunca uso esas funciones así que no las extraño.

Privateer Privateer
24 sept 2015 19:34:53

@urok93 A veces uso get_posts() cuando necesito obtener posts relacionados de ACF, especialmente si solo hay uno. Aunque para estandarizar mis plantillas estoy considerando reescribirlas como instancias de WP_Query.

Slam Slam
14 feb 2018 02:15:49

@urok93 Casi siempre uso get_posts porque no mantiene estado, mientras que WP_Query altera quién sabe qué variables globales.

tklodd tklodd
12 ene 2022 01:56:53
Mostrar los 8 comentarios restantes
1
65

Existen dos contextos diferentes para los bucles (loops):

  • El bucle principal que ocurre basado en la solicitud URL y se procesa antes de cargar las plantillas
  • Los bucles secundarios que ocurren de cualquier otra manera, llamados desde archivos de plantilla o de otro modo

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

Para modificar el bucle principal

  • no uses query_posts()
  • usa el filtro pre_get_posts con la verificación $query->is_main_query()
  • alternativamente usa el filtro request (un poco demasiado brusco, por lo que lo anterior es mejor)

Para ejecutar un bucle secundario

Usa new WP_Query o get_posts() que son prácticamente intercambiables (el último es un envoltorio ligero del primero).

Para limpiar

Usa wp_reset_query() si utilizaste query_posts() o manipulaste directamente la variable global $wp_query - por lo que casi nunca lo necesitarás.

Usa wp_reset_postdata() si utilizaste the_post() o setup_postdata() o manipulaste la variable global $post y necesitas restaurar el estado inicial de las cosas relacionadas con el post.

1 may 2012 16:27:52
Comentarios

Rarst se refería a wp_reset_postdata()

Gregory Gregory
1 jun 2012 12:18:31
4
26

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

  1. Quieres mostrar una lista de entradas o entradas de tipos de contenido personalizados en una página (usando una plantilla de página)

  2. Quieres que la paginación de esas entradas funcione

¿Por qué querrías mostrarlo en una página en lugar de usar una plantilla de archivo?

  1. Es más intuitivo para un administrador (¿tu cliente?) - pueden ver la página en 'Páginas'

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

  3. Si deseas mostrar contenido adicional (texto, imagen destacada o cualquier meta contenido personalizado) en la plantilla, puedes obtenerlo fácilmente de la página (y también tiene más sentido para el cliente). Si usaras una plantilla de archivo, necesitarías codificar el contenido adicional o usar, por ejemplo, opciones del tema/plugin (lo que lo hace menos intuitivo para el cliente)

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

/**
 * Nombre de la plantilla: Página de entradas
 */

while(have_posts()) { // bucle principal original - contenido de la página
  the_post();
  the_title(); // título de la página
  the_content(); // contenido de la página
  // etc...
}

// ahora mostramos la lista de nuestras entradas de tipo de contenido personalizado

// primero obtenemos los parámetros de paginación
$paged = 1;
if(get_query_var('paged')) {
  $paged = get_query_var('paged');
} elseif(get_query_var('page')) {
  $paged = get_query_var('page');
}

// consultamos las entradas y reemplazamos la consulta principal (página) con esta (para que la paginación funcione)
query_posts(array('post_type' => 'my_post_type', 'post_status' => 'publish', 'paged' => $paged));

// paginación
next_posts_link();
previous_posts_link();

// bucle
while(have_posts()) {
  the_post();
  the_title(); // título de tu entrada de tipo de contenido personalizado
  the_content(); // contenido de tu entrada de tipo de contenido personalizado
}

wp_reset_query(); // establece la consulta principal (global $wp_query) a la consulta original de la página (la obtiene de la variable global $wp_the_query) y restablece los datos de la entrada

// Entonces, ahora podemos mostrar el contenido relacionado con la página nuevamente (si lo deseamos)
while(have_posts()) { // bucle principal original - contenido de la página
  the_post();
  the_title(); // título de la página
  the_content(); // contenido de la página
  // etc...
}

Ahora, para ser completamente claros, también podríamos evitar usar query_posts() aquí y usar WP_Query en su lugar - así:

// ...

global $wp_query;
$wp_query = new WP_Query(array('tus parámetros de consulta aquí')); // establece la nueva consulta personalizada como consulta principal

// tu bucle de tipo de contenido personalizado aquí

wp_reset_query();

// ...

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

16 sept 2012 10:34:09
Comentarios

Brian, gracias por eso. He estado luchando para que pre_get_posts funcione en una página en EXACTAMENTE el escenario que describes: el cliente necesita agregar campos personalizados/contenido a lo que de otro modo sería una página de archivo, por lo que se necesita crear una "página"; el cliente necesita ver algo para agregar al menú de navegación, ya que agregar un enlace personalizado les resulta difícil; etc. ¡+1 de mi parte!

Will Lanni Will Lanni
13 dic 2012 13:07:49

Eso también se puede hacer usando "pre_get_posts". Lo hice para tener una "página de inicio estática" que muestre 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: http://wordpress.stackexchange.com/questions/30851/how-to-use-a-custom-post-type-archive-as-front-page/30854

En resumen, todavía no hay un escenario más legítimo para usar query_posts ;)

2ndkauboy 2ndkauboy
12 ene 2015 16:47:27

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 los peores escenarios más del doble de la cantidad de trabajo necesario o más. Aunque es fácil de usar, la función también es propensa a confusiones y problemas posteriores." Fuente http://codex.wordpress.org/Function_Reference/query_posts

Claudiu Creanga Claudiu Creanga
9 mar 2015 18:31:50

Esta respuesta está completamente equivocada. Puedes crear una "Página" en WP con la misma URL que el tipo de contenido personalizado. Por ejemplo, si tu CPT es Bananas, puedes tener una página llamada Bananas con la misma URL. Entonces terminarías con siteurl.com/bananas. Siempre que tengas archive-bananas.php en la carpeta de tu tema, usará la plantilla y "anulará" esa página en su lugar. Como se menciona en uno de los otros comentarios, usar este "método" crea el doble de trabajo para WP, por lo que NUNCA debería usarse.

Hybrid Web Dev Hybrid Web Dev
19 jun 2015 23:07:03
1
11

Modifico la consulta de WordPress desde functions.php:

//desafortunadamente, la condición "IS_PAGE" no funciona en pre_get_posts (es un comportamiento de WORDPRESS)
//por lo que puedes usar `add_filter('posts_where', ....);`    O   modificar la consulta de "PAGE" directamente en el archivo de plantilla

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%')"; 
}
23 ene 2015 12:11:29
Comentarios

estaría interesado en ver este ejemplo pero con la cláusula where aplicada a meta personalizados.

Andrew Welch Andrew Welch
3 mar 2017 18:20:07
3

Solo para esbozar algunas mejoras a la respuesta aceptada ya que WordPress ha evolucionado con el tiempo y algunas cosas son diferentes ahora (cinco años después):

pre_get_posts es un filtro, para alterar cualquier consulta. Se usa más comúnmente para alterar solo la 'consulta principal':

En realidad es un action hook. No es un filtro, y afectará a cualquier consulta.

La consulta principal aparece en tus plantillas como:

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

En realidad, esto tampoco es cierto. La función have_posts itera el objeto global $wp_query que no está relacionado únicamente con la consulta principal. global $wp_query; puede ser alterado también con consultas secundarias.

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

get_posts()

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

En realidad, actualmente WP_Query es una clase, por lo que tenemos una instancia de una clase.


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

28 dic 2016 03:26:41
Comentarios

Técnicamente, todo son filtros bajo el capó, las acciones son simplemente un filtro básico. Pero tienes razón aquí, es una acción que pasa un argumento por referencia, que es cómo difiere de acciones más simples.

Milo Milo
28 dic 2016 04:16:33

get_posts devuelve un array de objetos de publicación, no un objeto WP_Query, así que eso sigue siendo correcto. Y WP_Query siempre ha sido una clase, instancia de una clase = objeto.

Milo Milo
28 dic 2016 04:16:49

Gracias, @Milo, correcto, por alguna razón tenía un modelo demasiado simplificado en mi cabeza.

prosti prosti
28 dic 2016 10:51:18