Kiedy używać WP_query (), query_posts () i pre_get_posts

159

Przeczytałem @ nacin's Nie znasz zapytania wczoraj i wysłano mi trochę pytającej króliczej nory. Przed wczoraj (niesłusznie) korzystałem query_posts()ze wszystkich moich potrzeb związanych z zapytaniami. Teraz jestem trochę mądrzejszy w używaniu WP_Query(), ale nadal mam kilka szarych obszarów.

Co myślę, że wiem na pewno:

Jeśli robię dodatkowe pętle w dowolnym miejscu na stronie - na pasku bocznym, w stopce, wszelkiego rodzaju „powiązanych postach” itp. - chcę z nich korzystać WP_Query(). Mogę używać tego wielokrotnie na jednej stronie bez szkody. (dobrze?).

Czego nie wiem na pewno

  1. Kiedy używam @ nacin w pre_get_posts wersetach WP_Query()? Czy mam pre_get_poststeraz używać do wszystkiego?
  2. Kiedy chcę zmodyfikować pętlę na stronie szablonu - powiedzmy, że chcę zmodyfikować stronę archiwum systematyki - czy mogę usunąć if have_posts : while have_posts : the_postczęść i napisać własną WP_Query()? Czy mogę zmodyfikować dane wyjściowe za pomocą pre_get_postsmojego pliku functions.php?

tl; dr

Zasady tl; dr, które chciałbym z tego wyciągnąć, to:

  1. Nigdy query_postswięcej nie używaj
  2. Jeśli uruchamiasz wiele zapytań na jednej stronie, użyj WP_Query()
  3. Podczas modyfikowania pętli wykonaj to __________________.

Dzięki za wszelką mądrość

Terry

ps: Widziałem i czytałem: kiedy powinieneś używać WP_Query vs query_posts () vs get_posts ()? Co dodaje kolejny wymiar get_posts. Ale w ogóle się nie zajmuje pre_get_posts.

Dorsz
źródło
@saltcod, teraz jest inaczej, WordPress ewoluował, dodałem kilka komentarzy w porównaniu do przyjętej odpowiedzi tutaj .
prosti

Odpowiedzi:

145

Masz rację, mówiąc:

Nigdy query_postswięcej nie używaj

pre_get_posts

pre_get_poststo filtr służący do zmiany dowolnego zapytania. Najczęściej służy do zmiany tylko „głównego zapytania”:

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

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

(Sprawdziłbym również, czy is_admin()zwraca false - choć może to być zbędne). Główne zapytanie pojawia się w szablonach jako:

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

Jeśli kiedykolwiek poczujesz potrzebę edycji tej pętli - użyj pre_get_posts. tj. Jeśli masz ochotę użyć query_posts()- użyj pre_get_postszamiast tego.

WP_Query

Główne zapytanie jest ważną instancją pliku WP_Query object. WordPress używa go na przykład, aby zdecydować, którego szablonu użyć, a wszelkie argumenty przekazane do adresu URL (np. Pagination) są przekazywane do tej instancji WP_Queryobiektu.

W przypadku pętli wtórnych (np. Na paskach bocznych lub listach „pokrewnych postów”) będziesz chciał stworzyć własną oddzielną instancję WP_Queryobiektu. Na przykład

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

Uwaga wp_reset_postdata();- dzieje się tak, ponieważ pętla wtórna zastąpi $postzmienną globalną , która identyfikuje „bieżący post”. Zasadniczo resetuje to, że $postjesteśmy na.

get_posts ()

Jest to zasadniczo opakowanie dla oddzielnej instancji WP_Queryobiektu. Zwraca tablicę obiektów postu. Metody zastosowane w powyższej pętli nie są już dostępne. To nie jest „Pętla”, po prostu tablica obiektu pocztowego.

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

W odpowiedzi na twoje pytania

  1. Służy pre_get_postsdo zmiany głównego zapytania. Użyj osobnego WP_Queryobiektu (metoda 2) dla pętli wtórnych na stronach szablonu.
  2. Jeśli chcesz zmienić zapytanie głównej pętli, użyj pre_get_posts.
Stephen Harris
źródło
Czy istnieje więc scenariusz, w którym można przejść bezpośrednio do get_posts () zamiast WP_Query?
urok93
@drtanz - tak. Powiedz na przykład, że nie potrzebujesz paginacji lub lepkich postów u góry - w takich przypadkach get_posts()jest to bardziej wydajne.
Stephen Harris,
Ale czy nie dodałoby to dodatkowego zapytania, w którym moglibyśmy zmodyfikować pre_get_posts w celu zmodyfikowania głównego zapytania?
urok93
@drtanz - nie używałbyś get_posts()do głównego zapytania - jest to dla zapytań wtórnych.
Stephen Harris
1
@StephenHarris Right =) Jeśli użyjesz obiektu next_post () zamiast obiektu the_post, nie wchodzisz w globalne zapytanie i nie musisz pamiętać, aby użyć później wp_reset_postdata.
Privateer
55

Istnieją dwa różne konteksty dla pętli:

  • główna pętla, która dzieje się na podstawie żądania adresu URL i jest przetwarzana przed załadowaniem szablonów
  • wtórne pętle występujące w inny sposób, wywoływane z plików szablonów lub w inny sposób

Problem query_posts()polega na tym, że jest to pętla wtórna, która próbuje być główną, i kończy się niepowodzeniem. Zapomnij o tym, że istnieje.

Aby zmodyfikować główną pętlę

  • nie używaj query_posts()
  • użyj pre_get_postsfiltra z $query->is_main_query()czekiem
  • naprzemiennie użyj requestfiltra (trochę za szorstki, więc wyżej jest lepiej)

Aby uruchomić pętlę wtórną

Użyj new WP_Querylub get_posts()które są prawie wymienne (ten drugi jest cienkim opakowaniem dla byłego).

Posprzątać

Użyj, wp_reset_query()jeśli bezpośrednio query_posts()używałeś globalnej komunikacji $wp_query- więc prawie nigdy nie będziesz musiał tego robić.

Użyj wp_reset_postdata()jeśli używany the_post()lub setup_postdata()lub pomieszane z globalnym $posti trzeba przywrócić stan początkowy z Related Post-rzeczy.

Rarst
źródło
3
Rarst miał na myśliwp_reset_postdata()
Gregory
23

Istnieją uzasadnione scenariusze użycia query_posts($query), na przykład:

  1. Chcesz wyświetlić listę postów lub niestandardowych postów na stronie (za pomocą szablonu strony)

  2. Chcesz, aby paginacja tych postów działała

Dlaczego chcesz wyświetlać go na stronie zamiast szablonu archiwum?

  1. Jest to bardziej intuicyjne dla administratora (Twojego klienta?) - oni mogą zobaczyć stronę na „Stronach”

  2. Lepiej jest dodać go do menu (bez strony musieliby dodać adres URL bezpośrednio)

  3. Jeśli chcesz wyświetlić dodatkową treść (tekst, miniaturę postu lub dowolną niestandardową meta treść) w szablonie, możesz łatwo pobrać ją ze strony (i to wszystko ma sens również dla klienta). Sprawdź, czy korzystasz z szablonu archiwum, musisz albo zakodować dodatkową zawartość, albo użyć na przykład opcji motywu / wtyczki (co czyni to mniej intuicyjnym dla klienta)

Oto uproszczony przykładowy kod (który byłby na szablonie strony - np. 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...
}

Teraz, aby być całkowicie jasnym, moglibyśmy również uniknąć używania query_posts()tutaj i używać WP_Queryzamiast tego - tak:

// ...

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

// ...

Ale dlaczego mielibyśmy to robić, skoro mamy do dyspozycji tak ładną, małą funkcję?

Lukas Pecinka
źródło
1
Brian, dzięki za to. Staram się, aby pre_get_posts pracowało na stronie DOKŁADNIE w opisywanym scenariuszu: klient musi dodać niestandardowe pola / treść do strony, która w innym przypadku byłaby stroną archiwalną, więc trzeba utworzyć „stronę”; klient musi zobaczyć coś do dodania do menu nawigacyjnego, ponieważ dodanie niestandardowego linku powoduje ich uniknięcie; itd. +1 ode mnie!
Czy Lanni,
2
Można to również zrobić za pomocą „pre_get_posts”. Zrobiłem to, aby „statyczna strona główna” zawierała moje niestandardowe typy postów w niestandardowej kolejności i niestandardowym filtrze. Ta strona jest także paginowana. Sprawdź to pytanie, aby zobaczyć, jak to działa: wordpress.stackexchange.com/questions/30851/... Krótko mówiąc, nadal nie ma bardziej uzasadnionego scenariusza użycia query_posts;)
2ndkauboy
1
Ponieważ „Należy zauważyć, że użycie tego w celu zastąpienia głównego zapytania na stronie może wydłużyć czas ładowania strony, w najgorszym przypadku - więcej niż podwojenie wymaganej pracy lub więcej. Chociaż jest łatwa w użyciu, funkcja jest również podatna na zamieszanie i problemy później ”. Źródło codex.wordpress.org/Function_Reference/query_posts
Claudiu Creanga
Ta odpowiedź jest zła. Możesz utworzyć „Stronę” w WP o tym samym adresie URL, co niestandardowy typ postu. Np. Jeśli Twój CPT to Banany, możesz uzyskać stronę o nazwie Banany o tym samym adresie URL. Wtedy skończyłbyś z siteurl.com/bananas. Tak długo, jak masz plik archive-bananas.php w folderze motywu, będzie on używał szablonu i „zastąpi” tę stronę. Jak stwierdzono w jednym z pozostałych komentarzy, użycie tej „metody” powoduje dwukrotne obciążenie dla WP, dlatego NIE powinno się jej nigdy używać.
Hybrid Web Dev,
8

Zmieniam zapytanie WordPress z 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
źródło
chciałbym zobaczyć ten przykład, ale gdzie klauzula dotyczy niestandardowych meta.
Andrew Welch,
6

Wystarczy przedstawić kilka poprawek do zaakceptowanej odpowiedzi, ponieważ WordPress ewoluował w czasie, a niektóre rzeczy są teraz inne (pięć lat później):

pre_get_poststo filtr służący do zmiany dowolnego zapytania. Najczęściej służy do zmiany tylko „głównego zapytania”:

Właściwie to hak akcji. Nie filtr, i wpłynie na każde zapytanie.

Główne zapytanie pojawia się w szablonach jako:

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

W rzeczywistości nie jest to również prawdą. Funkcja have_postsiteruje global $wp_queryobiekt, który nie jest powiązany tylko z głównym zapytaniem. global $wp_query;mogą być również zmieniane za pomocą dodatkowych zapytań.

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

get_posts ()

Jest to zasadniczo opakowanie oddzielnej instancji obiektu WP_Query.

Właściwie w dzisiejszych czasach WP_Queryjest klasa, więc mamy instancję klasy.


Podsumowując: w tym czasie @StephenHarris najprawdopodobniej napisał wszystko, co było prawdą, ale z czasem rzeczy w WordPressie uległy zmianie.

prosti
źródło
Technicznie rzecz biorąc, wszystkie filtry są pod maską, akcje są tylko prostym filtrem. Ale masz rację tutaj, jest to akcja, która przekazuje argument przez referencję, co różni się od prostszych akcji.
Milo
get_postszwraca tablicę obiektów postu, a nie WP_Queryobiekt, więc to rzeczywiście jest poprawne. i WP_Queryzawsze była klasą, instancją klasy = obiekt.
Milo
Dzięki, @Milo, poprawnie z jakiegoś powodu miałem w mojej głowie uproszczony model.
prosti