Wann ist WP_query (), query_posts () und pre_get_posts zu verwenden?

159

Ich habe gestern bei @nacin's " Du kennst Query" gelesen und wurde ein bisschen in ein Kaninchenloch geschickt, in dem nachgefragt wurde. Vor gestern habe ich (zu Unrecht) query_posts()alle meine Abfrageanforderungen erfüllt. Jetzt bin ich ein bisschen weiser im Umgang mit WP_Query(), habe aber noch einige Grauzonen.

Was ich mit Sicherheit zu wissen glaube:

Wenn ich irgendwo auf einer Seite zusätzliche Schleifen mache - in der Seitenleiste, in einer Fußzeile, in "verwandten Beiträgen" usw. - möchte ich diese verwenden WP_Query(). Ich kann das wiederholt auf einer einzelnen Seite ohne Schaden verwenden. (richtig?).

Was ich nicht sicher weiß

  1. Wann verwende ich @ nacin des pre_get_posts vs. WP_Query()? Soll ich pre_get_postsjetzt für alles verwenden?
  2. Wenn ich die Schleife in einer Vorlagenseite ändern möchte - sagen wir, ich möchte eine Taxonomie-Archivseite ändern - entferne ich den if have_posts : while have_posts : the_postTeil und schreibe meinen eigenen WP_Query()? Oder ändere ich die Ausgabe pre_get_postsin meiner functions.php Datei?

tl; dr

Die Regeln, die ich hieraus ziehen möchte, sind:

  1. Nie query_postsmehr benutzen
  2. Wenn Sie mehrere Abfragen auf einer Seite ausführen, verwenden Sie WP_Query()
  3. Wenn Sie eine Schleife ändern, gehen Sie wie folgt vor: __________________.

Danke für jede Weisheit

Terry

PS: Ich habe gesehen und gelesen: Wann sollten Sie WP_Query vs query_posts () vs get_posts () verwenden? Was eine weitere Dimension hinzufügt - get_posts. Beschäftigt sich aber überhaupt nicht pre_get_posts.

Salz Kabeljau
quelle
@saltcod, jetzt ist anders, WordPress hat sich weiterentwickelt, ich habe ein paar Kommentare im Vergleich zur akzeptierten Antwort hier hinzugefügt .
Prosti

Antworten:

145

Sie haben Recht zu sagen:

Nie query_postsmehr benutzen

pre_get_posts

pre_get_postsist ein Filter zum Ändern von Abfragen. Es wird am häufigsten verwendet, um nur die 'Hauptabfrage' zu ändern:

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

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

(Ich würde auch prüfen, ob die is_admin()Rückgabe falsch ist - obwohl dies redundant sein kann.) Die Hauptabfrage erscheint in Ihren Vorlagen als:

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

Wenn Sie jemals das Bedürfnis haben, diese Schleife zu bearbeiten, verwenden Sie pre_get_posts. Wenn Sie versucht sind zu verwenden, query_posts()verwenden Sie pre_get_postsstattdessen.

WP_Query

Die Hauptabfrage ist eine wichtige Instanz von a WP_Query object. WordPress verwendet es, um zu entscheiden, welche Vorlage verwendet werden soll, und alle Argumente, die an die URL übergeben werden (z. B. Paginierung), werden in diese Instanz des WP_QueryObjekts geleitet.

Für sekundäre Schleifen (z. B. in Seitenleisten oder Listen mit verwandten Posts) möchten Sie eine eigene Instanz des WP_QueryObjekts erstellen . Z.B

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

Hinweis wp_reset_postdata();- Dies liegt daran, dass die sekundäre Schleife die globale $postVariable überschreibt , die den 'aktuellen Beitrag' identifiziert. Dies setzt im Wesentlichen das zurück, worauf $postwir uns befinden.

get_posts ()

Dies ist im Wesentlichen ein Wrapper für eine separate Instanz eines WP_QueryObjekts. Dies gibt ein Array von Post-Objekten zurück. Die in der obigen Schleife verwendeten Methoden stehen Ihnen nicht mehr zur Verfügung. Dies ist keine 'Schleife', sondern nur ein Array von Post-Objekten.

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

Als Antwort auf Ihre Fragen

  1. Verwenden Sie pre_get_posts, um Ihre Hauptabfrage zu ändern. Verwenden Sie ein separates WP_QueryObjekt (Methode 2) für sekundäre Schleifen in den Vorlagenseiten.
  2. Wenn Sie die Abfrage der Hauptschleife ändern möchten, verwenden Sie pre_get_posts.
Stephen Harris
quelle
Gibt es ein Szenario, in dem man direkt zu get_posts () anstatt zu WP_Query wechseln würde?
Urok93
@drtanz - ja. Nehmen wir zum Beispiel an, Sie brauchen keine Paginierung oder klebrige Pfosten an der Spitze - in diesen Fällen get_posts()ist dies effizienter.
Stephen Harris
Aber würde das nicht eine zusätzliche Abfrage hinzufügen, bei der wir nur pre_get_posts ändern könnten, um die Hauptabfrage zu ändern?
Urok93
@drtanz - Sie würden es nicht get_posts()für die Hauptabfrage verwenden - es ist für sekundäre Abfragen.
Stephen Harris
1
@StephenHarris Right =) Wenn Sie next_post () für das Objekt anstelle von the_post verwenden, treten Sie nicht in die globale Abfrage ein und müssen nicht daran denken, danach wp_reset_postdata zu verwenden.
Privateer
55

Es gibt zwei verschiedene Kontexte für Schleifen:

  • Hauptschleife , die auf der Grundlage einer URL-Anfrage erfolgt und verarbeitet wird, bevor Vorlagen geladen werden
  • Sekundärschleifen , die auf andere Weise auftreten und aus Vorlagendateien oder auf andere Weise aufgerufen werden

Das Problem query_posts()ist, dass es eine sekundäre Schleife ist, die versucht, die Hauptschleife zu sein und kläglich versagt. Also vergiss, dass es existiert.

Hauptschleife ändern

  • nicht benutzen query_posts()
  • verwenden pre_get_postsFilter mit $query->is_main_query()Scheck
  • Alternativ requestFilter verwenden (etwas zu rau, daher besser oben)

Sekundäre Schleife ausführen

Verwenden Sie new WP_Queryoder get_posts()die ziemlich austauschbar sind (letzteres ist dünne Hülle für erstere).

Aufräumen

Verwenden wp_reset_query()Sie diese query_posts()Option, wenn Sie Global $wp_querydirekt verwendet haben oder damit in Konflikt geraten sind - dies wird so gut wie nie erforderlich sein.

Verwenden wp_reset_postdata()Sie diese Option, wenn Sie " global" verwendet haben the_post()oder damit setup_postdata()herumgespielt haben $postund den Anfangszustand von nachträglichen Dingen wiederherstellen müssen.

Rarst
quelle
3
Rarst meintewp_reset_postdata()
Gregory
23

Es gibt legitime Szenarien für die Verwendung query_posts($query), zum Beispiel:

  1. Sie möchten eine Liste von Posts oder benutzerdefinierten Posts auf einer Seite anzeigen (mithilfe einer Seitenvorlage).

  2. Sie möchten, dass die Paginierung dieser Posts funktioniert

Warum sollten Sie es nun auf einer Seite anzeigen, anstatt eine Archivvorlage zu verwenden?

  1. Für einen Administrator (Ihren Kunden?) Ist es intuitiver - er kann die Seite unter "Seiten" sehen.

  2. Es ist besser, es zu Menüs hinzuzufügen (ohne die Seite müssten sie die URL direkt hinzufügen)

  3. Wenn Sie zusätzlichen Inhalt (Text, Miniaturansicht des Beitrags oder benutzerdefinierten Metainhalt) in der Vorlage anzeigen möchten, können Sie ihn problemlos von der Seite abrufen (und dies ist auch für den Kunden sinnvoller). Wenn Sie eine Archivvorlage verwendet haben, müssen Sie entweder den zusätzlichen Inhalt fest codieren oder beispielsweise Themen- / Plug-in-Optionen verwenden (dies macht es für den Kunden weniger intuitiv).

Hier ist ein vereinfachter Beispielcode (der sich auf Ihrer Seitenvorlage befinden würde - zB 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...
}

Nun, um ganz klar zu sein, könnten wir auch hier vermeiden query_posts()und WP_Querystattdessen Folgendes verwenden:

// ...

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

// ...

Aber warum sollten wir das tun, wenn wir so eine nette kleine Funktion zur Verfügung haben?

Lukas Pecinka
quelle
1
Brian, danke dafür. Ich hatte Mühe, pre_get_posts dazu zu bringen, auf einer Seite genau in dem von Ihnen beschriebenen Szenario zu arbeiten: Der Client muss benutzerdefinierte Felder / Inhalte zu einer Archivseite hinzufügen, sodass eine "Seite" erstellt werden muss. Der Client muss etwas sehen, das er dem Navigationsmenü hinzufügen kann, da das Hinzufügen eines benutzerdefinierten Links diesen entgeht. usw. +1 von mir!
Will Lanni
2
Dies kann auch mit "pre_get_posts" erfolgen. Ich habe das gemacht, um eine "statische Startseite" zu haben, die meine benutzerdefinierten Beitragstypen in einer benutzerdefinierten Reihenfolge und mit einem benutzerdefinierten Filter auflistet. Diese Seite ist auch paginiert. Sehen Sie sich diese Frage zu sehen , wie es funktioniert: wordpress.stackexchange.com/questions/30851/... Also kurz gesagt, es für die Verwendung von query_posts noch keine legitimen Szenario ist;)
2ndkauboy
1
"Es sollte beachtet werden, dass die Verwendung dieser Option zum Ersetzen der Hauptabfrage auf einer Seite die Ladezeiten der Seite verlängern kann. Im schlimmsten Fall kann sich der Arbeitsaufwand mehr als verdoppeln oder mehr. Die Funktion ist zwar einfach zu verwenden, birgt jedoch auch die Gefahr von Verwirrung und Probleme später. " Quelle codex.wordpress.org/Function_Reference/query_posts
Claudiu Creanga
Diese Antwort ist in jeder Hinsicht falsch. Sie können in WP eine "Seite" mit derselben URL wie der Typ "Benutzerdefinierter Beitrag" erstellen. ZB wenn Ihr CPT Bananen ist, können Sie eine Seite mit dem Namen Bananen mit der gleichen URL erhalten. Dann würden Sie mit siteurl.com/bananas enden. Solange du archive-bananas.php in deinem Theme-Ordner hast, wird stattdessen die Vorlage verwendet und diese Seite "überschrieben". Wie in einem der anderen Kommentare angegeben, führt die Verwendung dieser "Methode" zu einer doppelten Arbeitsbelastung für WP und sollte daher niemals verwendet werden.
Hybrid Web Dev
8

Ich ändere die WordPress-Abfrage von 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
quelle
würde mich interessieren, dieses Beispiel zu sehen, aber wo Klausel auf benutzerdefinierten Meta ist.
Andrew Welch
6

Um nur einige Verbesserungen an der akzeptierten Antwort zu skizzieren, seit sich WordPress im Laufe der Zeit weiterentwickelt hat und einige Dinge jetzt (fünf Jahre später) anders sind:

pre_get_postsist ein Filter zum Ändern von Abfragen. Es wird am häufigsten verwendet, um nur die 'Hauptabfrage' zu ändern:

Eigentlich ist das ein Aktionshaken. Kein Filter, und es wirkt sich auf jede Abfrage aus.

Die Hauptabfrage erscheint in Ihren Vorlagen als:

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

Eigentlich stimmt das auch nicht. Die Funktion have_postsiteriert das global $wp_queryObjekt, das sich nicht nur auf die Hauptabfrage bezieht . global $wp_query;kann auch mit den sekundären Abfragen geändert werden.

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

get_posts ()

Dies ist im Wesentlichen ein Wrapper für eine separate Instanz eines WP_Query-Objekts.

Tatsächlich ist heutzutage WP_Queryeine Klasse, also haben wir eine Instanz einer Klasse.


Fazit: Zu der Zeit, als @StephenHarris schrieb, war dies höchstwahrscheinlich alles wahr, aber im Laufe der Zeit wurden die Dinge in WordPress geändert.

prosti
quelle
Technisch gesehen sind alle Filter unter der Haube, Aktionen sind nur ein einfacher Filter. Aber Sie haben Recht, es ist eine Aktion, die ein Argument als Referenz weitergibt, was sich von einfacheren Aktionen unterscheidet.
Milo
get_postsGibt ein Array von Post-Objekten zurück, kein WP_QueryObjekt, das ist also immer noch korrekt. und war WP_Queryschon immer eine Klasse, eine Instanz eines class = object.
Milo
Danke, @Milo, aus irgendeinem Grund hatte ich das Modell in meinem Kopf zu stark vereinfacht.
Prosti