Quand utiliser WP_query (), query_posts () et pre_get_posts

159

J'ai lu @ nacin Tu ne connais pas Query hier et j'ai été envoyé à un trou de lapin interrogateur. Avant hier, j'utilisais (à tort) query_posts()pour tous mes besoins d'interrogation. Maintenant, je suis un peu plus sage sur l'utilisation WP_Query(), mais j'ai encore des zones grises.

Ce que je pense savoir à coup sûr:

Si je fais des boucles supplémentaires n'importe où sur une page - dans la barre latérale, dans un pied de page, dans n'importe quel type de "messages liés", etc., je veux utiliser WP_Query(). Je peux l'utiliser à plusieurs reprises sur une seule page sans aucun dommage. (droite?).

Ce que je ne sais pas avec certitude

  1. Quand est-ce que j'utilise @ nacin pre_get_posts vs WP_Query()? Devrais-je utiliser pre_get_postspour tout maintenant?
  2. Lorsque je souhaite modifier la boucle dans une page de modèle (disons que je souhaite modifier une page d'archive de taxonomie), dois-je supprimer la if have_posts : while have_posts : the_postpartie et écrire la mienne WP_Query()? Ou dois-je modifier la sortie en utilisant pre_get_postsdans mon fichier functions.php?

tl; dr

Les règles que je voudrais en tirer sont les suivantes:

  1. Ne jamais utiliser query_postsplus
  2. Lorsque vous exécutez plusieurs requêtes sur une même page, utilisez WP_Query()
  3. Lorsque vous modifiez une boucle, faites ceci __________________.

Merci pour toute sagesse

Terry

ps: j'ai vu et lu: quand utiliser WP_Query vs query_posts () vs get_posts ()? Ce qui ajoute une autre dimension - get_posts. Mais ne traite pas pre_get_postsdu tout.

morue salée
la source
3
Doublon
dotancohen
@saltcod, c'est différent maintenant, WordPress a évolué, j'ai ajouté quelques commentaires par rapport à la réponse acceptée ici .
prosti

Réponses:

145

Vous avez raison de dire:

Ne jamais utiliser query_postsplus

pre_get_posts

pre_get_postsest un filtre, pour modifier toute requête. Il est le plus souvent utilisé pour modifier uniquement la "requête principale":

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

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

(Je voudrais également vérifier que is_admin()renvoie faux - bien que cela puisse être redondant.). La requête principale apparaît dans vos modèles en tant que:

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

Si vous ressentez le besoin de modifier cette boucle, utilisez-la pre_get_posts. Par exemple, si vous êtes tenté d’utiliser query_posts(), utilisez pre_get_postsplutôt.

WP_Query

La requête principale est une instance importante de a WP_Query object. WordPress l'utilise pour décider quel modèle utiliser, par exemple, et tous les arguments passés dans l'URL (par exemple, la pagination) sont tous canalisés dans cette instance de l' WP_Queryobjet.

Pour les boucles secondaires (par exemple, dans les barres latérales ou les listes de «publications connexes»), vous voudrez créer votre propre instance distincte de l' WP_Queryobjet. Par exemple

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

Avis wp_reset_postdata();- ceci est dû au fait que la boucle secondaire remplacera la $postvariable globale qui identifie la «publication actuelle». Cela remet essentiellement cela à la $postnous sommes sur.

get_posts ()

Il s'agit essentiellement d'un wrapper pour une instance distincte d'un WP_Queryobjet. Cela retourne un tableau d'objets post. Les méthodes utilisées dans la boucle ci-dessus ne vous sont plus disponibles. Ce n'est pas une "boucle", simplement un tableau d'objets post.

<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 réponse à vos questions

  1. Utilisez pre_get_postspour modifier votre requête principale. Utilisez un WP_Queryobjet séparé (méthode 2) pour les boucles secondaires dans les pages de modèle.
  2. Si vous souhaitez modifier la requête de la boucle principale, utilisez pre_get_posts.
Stephen Harris
la source
Y a-t-il un scénario dans lequel on irait directement à get_posts () plutôt qu'à WP_Query?
urok93
@drtanz - oui. Supposons, par exemple, que vous n’ayez pas besoin de pagination, ni de posts collants au sommet: dans ces cas, get_posts()c’est plus efficace.
Stephen Harris
Mais cela n’a-t-il pas ajouté une requête supplémentaire dans laquelle nous pourrions simplement modifier pré_get_posts pour modifier la requête principale?
urok93
@drtanz - vous n'utiliseriez pas get_posts()pour la requête principale - c'est pour les requêtes secondaires.
Stephen Harris
1
@StephenHarris Right =) Si vous utilisez next_post () sur l'objet au lieu d'utiliser the_post, vous n'intervenez pas dans la requête globale et vous n'avez pas besoin de vous rappeler d'utiliser ensuite wp_reset_postdata.
corsaire
55

Il existe deux contextes différents pour les boucles:

  • boucle principale qui se produit en fonction de la demande d'URL et qui est traitée avant le chargement des modèles
  • boucles secondaires qui se produisent de toute autre manière, appelées à partir de fichiers de modèle ou autrement

Le problème, query_posts()c’est que c’est une boucle secondaire qui essaie d’être principale et qui échoue lamentablement. Donc oubliez ça existe.

Pour modifier la boucle principale

  • ne pas utiliser query_posts()
  • utiliser le pre_get_postsfiltre avec $query->is_main_query()contrôle
  • alternativement utiliser le requestfiltre (un peu trop rugueux, donc c'est mieux, c'est mieux)

Pour exécuter la boucle secondaire

Utilisez new WP_Queryou get_posts()qui sont à peu près interchangeables (ce dernier est un emballage fin pour ancien).

Nettoyer

Utilisez-le wp_reset_query()si vous avez utilisé query_posts()ou joué $wp_querydirectement avec Global - vous n'aurez donc presque jamais besoin de le faire.

Utilisez-le wp_reset_postdata()si vous avez utilisé the_post()ou setup_postdata()joué avec Global $postet que vous devez restaurer l'état initial des éléments post-liés.

Rarst
la source
3
Rarst voulait direwp_reset_postdata()
Gregory
23

Il existe des scénarios légitimes d'utilisation query_posts($query), par exemple:

  1. Vous souhaitez afficher une liste de publications ou des publications de type publication personnalisée sur une page (à l'aide d'un modèle de page)

  2. Vous voulez faire de la pagination de ces messages

Maintenant, pourquoi voudriez-vous l'afficher sur une page au lieu d'utiliser un modèle d'archive?

  1. C'est plus intuitif pour un administrateur (votre client?) - il peut voir la page dans les 'Pages'

  2. C'est mieux pour l'ajouter aux menus (sans la page, il faudrait ajouter l'URL directement)

  3. Si vous souhaitez afficher du contenu supplémentaire (texte, miniature de publication ou tout méta contenu personnalisé) sur le modèle, vous pouvez facilement l'obtenir à partir de la page (et tout cela est plus logique pour le client également). Voyez si vous avez utilisé un modèle d’archive, vous devez coder en dur le contenu supplémentaire ou utiliser, par exemple, des options de thème / plugin (ce qui le rend moins intuitif pour le client).

Voici un exemple de code simplifié (qui se trouverait sur votre modèle de page - par exemple 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...
}

Maintenant, pour être parfaitement clair, nous pourrions éviter d’utiliser query_posts()ici aussi et utiliser à la WP_Queryplace - comme ceci:

// ...

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

// ...

Mais pourquoi ferions-nous cela alors que nous avons une si jolie petite fonction à sa disposition?

Lukas Pecinka
la source
1
Brian, merci pour ça. J'ai eu du mal à faire en sorte que pre_get_posts fonctionne sur une page dans EXACTEMENT le scénario que vous décrivez: le client doit ajouter des champs / du contenu personnalisés à ce qui serait autrement une page d'archive; une "page" doit donc être créée; le client doit voir quelque chose à ajouter au menu de navigation, car l'ajout d'un lien personnalisé leur échappe; etc. +1 de moi!
Will Lanni
2
Cela peut aussi être fait en utilisant "pre_get_posts". Je l'ai fait pour avoir une "page de garde statique" répertoriant mes types d'articles personnalisés dans un ordre personnalisé et avec un filtre personnalisé. Cette page est aussi paginée. Consultez cette question pour voir comment cela fonctionne: wordpress.stackexchange.com/questions/30851/… Donc, en bref, il n'y a pas encore de scénario légitime pour utiliser query_posts;)
2ndkauboy
1
Parce que "Il convient de noter que l’utilisation de cette option pour remplacer la requête principale sur une page peut augmenter les temps de chargement de la page, dans le pire des cas, il est même plus que de doubler la quantité de travail nécessaire ou plus. Bien que facile à utiliser, la fonction est également sujette à confusion et des problèmes plus tard. " Source codex.wordpress.org/Function_Reference/query_posts
Claudiu Creanga Le
Cette réponse est erronée. Vous pouvez créer une "page" dans WP avec la même URL que le type de publication personnalisé. Par exemple, si votre CPT est Bananas, vous pouvez obtenir une page nommée Bananas avec la même adresse URL. Ensuite, vous vous retrouvez avec siteurl.com/bananas. Tant que vous avez archive-bananas.php dans votre dossier de thèmes, il utilisera le modèle et "remplacera" cette page à la place. Comme indiqué dans l'un des autres commentaires, l'utilisation de cette "méthode" crée deux fois la charge de travail de WP et ne doit donc JAMAIS être utilisée.
Hybrid Web Dev
8

Je modifie la requête WordPress à partir de 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
la source
serait intéressé de voir cet exemple, mais où clause est sur méta personnalisé.
Andrew Welch
6

Juste pour souligner quelques améliorations à la réponse acceptée depuis que WordPress a évolué au fil du temps et certaines choses sont différentes maintenant (cinq ans plus tard):

pre_get_postsest un filtre, pour modifier toute requête. Il est le plus souvent utilisé pour modifier uniquement la "requête principale":

Est en fait un crochet d’action. Ce n'est pas un filtre et cela affectera toute requête.

La requête principale apparaît dans vos modèles en tant que:

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

En fait, ce n'est pas vrai non plus. La fonction have_postsitère l' global $wp_queryobjet qui n'est pas lié uniquement à la requête principale. global $wp_query;peut être modifié avec les requêtes secondaires aussi.

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

get_posts ()

Il s'agit essentiellement d'un wrapper pour une instance distincte d'un objet WP_Query.

En fait, de nos jours WP_Queryest une classe, nous avons donc une instance de classe.


Pour conclure: à l'époque où @StephenHarris a très probablement écrit, tout cela était vrai, mais au fil du temps, les choses ont changé dans WordPress.

prosti
la source
Techniquement, tout est sous filtre, les actions ne sont qu'un simple filtre. Mais vous avez raison, c’est une action qui transmet un argument par référence, ce qui la différencie des actions plus simples.
Milo
get_postsretourne un tableau d'objets post, pas un WP_Queryobjet, donc c'est toujours correct. et WP_Querya toujours été une classe, une instance d'un objet class =.
Milo
Merci, @Milo, correct, pour une raison quelconque, j'avais un modèle trop simpliste dans ma tête.
prosti