Când să folosești WP_query(), query_posts() și pre_get_posts
Am citit articolul @nacin "You don't know Query" ieri și am fost purtat într-o serie de întrebări despre interogări. Înainte de ieri, foloseam (greșit) query_posts()
pentru toate nevoile mele de interogare. Acum sunt puțin mai înțelept în ceea ce privește utilizarea WP_Query()
, dar încă am unele zone neclare.
Ce cred că știu sigur:
Dacă creez bucle suplimentare oriunde pe o pagină—în sidebar, în footer, orice fel de "articole conexe", etc—ar trebui să folosesc WP_Query()
. Pot să o folosesc în mod repetat pe o singură pagină fără probleme. (corect?)
Ce nu știu sigur
- Când folosesc @nacin's
pre_get_posts
versusWP_Query()
? Ar trebui să folosescpre_get_posts
pentru tot acum? - Când vreau să modific bucla într-o pagină șablon — să zicem că vreau să modific o pagină de arhivă taxonomie — elimin partea cu
if have_posts : while have_posts : the_post
și scriu propriulWP_Query()
? Sau modific output-ul folosindpre_get_posts
în fișierul functions.php?
tl;dr
Regulile tl;dr pe care aș vrea să le extrag din asta sunt:
- Nu mai folosiți niciodată
query_posts
- Când rulați interogări multiple pe o singură pagină, folosiți
WP_Query()
- Când modificați o buclă, faceți asta __________________.
Mulțumesc pentru orice sfat
Terry
ps: Am văzut și citit: Când ar trebui să folosești WP_Query vs query_posts() vs get_posts()? Care adaugă o altă dimensiune — get_posts
. Dar nu tratează deloc pre_get_posts
.

Aveți dreptate să spuneți:
Nu mai folosiți niciodată
query_posts
pre_get_posts
pre_get_posts
este un filtru pentru modificarea oricărei interogări. Cel mai des este folosit pentru a modifica doar 'interogarea principală':
add_action('pre_get_posts','wpse50761_alter_query');
function wpse50761_alter_query($query){
if( $query->is_main_query() ){
//Faceți ceva cu interogarea principală
}
}
(Aș verifica și dacă is_admin()
returnează false - deși acest lucru poate fi redundant.). Interogarea principală apare în șabloanele dumneavoastră ca:
if( have_posts() ):
while( have_posts() ): the_post();
//Bucla
endwhile;
endif;
Dacă vreodată simțiți nevoia să editați această buclă - folosiți pre_get_posts
. Adică, dacă sunteți tentat să folosiți query_posts()
- folosiți pre_get_posts
în schimb.
WP_Query
Interogarea principală este o instanță importantă a unui obiect WP_Query
. WordPress o folosește pentru a decide ce șablon să utilizeze, de exemplu, și orice argumente transmise în URL (de ex. paginare) sunt direcționate către acea instanță a obiectului WP_Query
.
Pentru bucle secundare (de ex. în bare laterale sau liste de 'postări conexe') veți dori să creați propria instanță separată a obiectului WP_Query
. De ex.:
$my_secondary_loop = new WP_Query(...);
if( $my_secondary_loop->have_posts() ):
while( $my_secondary_loop->have_posts() ): $my_secondary_loop->the_post();
//Bucla secundară
endwhile;
endif;
wp_reset_postdata();
Observați wp_reset_postdata();
- acest lucru se datorează faptului că bucla secundară va suprascrie variabila globală $post
care identifică 'postarea curentă'. Aceasta resetează în esență acea variabilă la $post
pe care ne aflăm.
get_posts()
Aceasta este în esență un wrapper pentru o instanță separată a unui obiect WP_Query
. Aceasta returnează un array de obiecte post. Metodele utilizate în bucla de mai sus nu mai sunt disponibile pentru dumneavoastră. Aceasta nu este o 'Buclă', ci pur și simplu un array de obiecte 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>
Ca răspuns la întrebările dumneavoastră
- Folosiți
pre_get_posts
pentru a modifica interogarea principală. Folosiți un obiectWP_Query
separat (metoda 2) pentru buclele secundare în paginile de șabloane. - Dacă doriți să modificați interogarea buclei principale, folosiți
pre_get_posts
.

Deci există vreun scenariu în care cineva ar folosi direct get_posts() în loc de WP_Query?

@drtanz - da. De exemplu, dacă nu ai nevoie de paginare sau de postări lipicioase în partea de sus - în aceste cazuri get_posts()
este mai eficient.

Dar nu ar adăuga asta o interogare suplimentară în loc să putem modifica pur și simplu pre_get_posts pentru a modifica interogarea principală?

@drtanz - tu nu ai folosi get_posts()
pentru interogarea principală - este pentru interogări secundare.

În exemplul tău cu WP_Query, dacă schimbi $my_secondary_loop->the_post(); în $my_post = $my_secondary_loop->next_post(); poți evita să uiți să folosești wp_reset_postdata() atâta timp cât folosești $my_post pentru a face ceea ce trebuie să faci.

@Privateer Nu chiar, WP_Query::get_posts()
setează global $post;

@StephenHarris Tocmai am parcurs clasa de interogare și nu o găsesc. Am verificat mai ales pentru că niciodată nu folosesc wp_reset_postdata pentru că întotdeauna fac interogările în acest fel. Creezi un obiect nou și toate rezultatele sunt conținute în el.

@Privateer - scuze, greșeală de tipar, WP_Query::the_post()
, vezi: https://github.com/WordPress/WordPress/blob/759f3d894ce7d364cf8bfc755e483ac2a6d85653/wp-includes/query.php#L3732

@StephenHarris Corect =) Dacă folosești next_post() pe obiect în loc să folosești the_post, nu afectezi interogarea globală și nu trebuie să-ți amintești să folosești wp_reset_postdata după aceea.

@Privateer Ah, îmi cer scuze, se pare că m-am încurcat. Ai dreptate (dar nu ai putea folosi funcții care se referă la globalul $post
, de exemplu the_title()
, the_content()
).

Adevărat =) Eu nu folosesc niciodată așa ceva, așa că nu simt lipsa lor.

@urok93 Uneori folosesc get_posts()
când am nevoie să obțin postări legate prin ACF, mai ales dacă e doar una. Dar pentru a standardiza șabloanele, mă gândesc să le rescriu ca instanțe WP_Query.

Există două contexte diferite pentru buclele (loops):
- bucla principală care are loc în funcție de cererea URL-ului și este procesată înainte de încărcarea șabloanelor
- bucla secundară care are loc în orice alt mod, apelată din fișierele șabloane sau altfel
Problema cu query_posts()
este că este o buclă secundară care încearcă să fie cea principală și eșuează lamentabil. Prin urmare, uită că există.
Pentru a modifica bucla principală
- nu folosi
query_posts()
- folosește filtrul
pre_get_posts
cu verificarea$query->is_main_query()
- alternativ, poți folosi filtrul
request
(puțin prea dur, așa că varianta anterioară este mai bună)
Pentru a rula o buclă secundară
Folosește new WP_Query
sau get_posts()
care sunt practic interschimbabile (ultima este un wrapper subțire pentru prima).
Pentru curățare
Folosește wp_reset_query()
dacă ai folosit query_posts()
sau ai modificat direct variabila globală $wp_query
- deci aproape niciodată nu vei avea nevoie.
Folosește wp_reset_postdata()
dacă ai folosit the_post()
sau setup_postdata()
sau ai modificat variabila globală $post
și trebuie să restaurezi starea inițială a elementelor legate de postare.

Există scenarii legitime pentru utilizarea funcției query_posts($query)
, de exemplu:
Doriți să afișați o listă de articole sau articole de tip custom-post-type pe o pagină (folosind un șablon de pagină)
Doriți ca paginarea acestor articole să funcționeze
De ce ați dori să le afișați pe o pagină în loc să folosiți un șablon de arhivă?
Este mai intuitiv pentru un administrator (clientul dvs.) - pot vedea pagina în lista 'Pagini'
Este mai ușor de adăugat în meniuri (fără pagina, ar trebui să adauge URL-ul direct)
Dacă doriți să afișați conținut suplimentar (text, imagine reprezentativă sau orice meta-conținut personalizat) pe șablon, îl puteți obține ușor de pe pagină (și totul are mai mult sens și pentru client). Dacă ați folosit un șablon de arhivă, ați fi nevoit fie să introduceți conținutul suplimentar direct în cod, fie să folosiți opțiuni din temă/plugin (ceea ce este mai puțin intuitiv pentru client)
Iată un exemplu simplificat de cod (care ar fi în șablonul paginii - de ex. page-page-of-posts.php):
/**
* Template Name: Pagină cu Articole
*/
while(have_posts()) { // bucla principală originală - conținutul paginii
the_post();
the_title(); // titlul paginii
the_content(); // conținutul paginii
// etc...
}
// acum afișăm lista de articole de tip custom-post-type
// mai întâi obținem parametrii de paginare
$paged = 1;
if(get_query_var('paged')) {
$paged = get_query_var('paged');
} elseif(get_query_var('page')) {
$paged = get_query_var('page');
}
// interogăm articolele și înlocuim interogarea principală (pagina) cu aceasta (pentru ca paginarea să funcționeze)
query_posts(array('post_type' => 'my_post_type', 'post_status' => 'publish', 'paged' => $paged));
// paginare
next_posts_link();
previous_posts_link();
// buclă
while(have_posts()) {
the_post();
the_title(); // titlul articolului de tip custom-post-type
the_content(); // conținutul articolului de tip custom-post-type
}
wp_reset_query(); // resetează interogarea principală (global $wp_query) la interogarea originală a paginii (o obține din variabila globală $wp_the_query) și resetează datele postului
// Acum putem afișa din nou conținutul asociat paginii (dacă dorim)
while(have_posts()) { // bucla principală originală - conținutul paginii
the_post();
the_title(); // titlul paginii
the_content(); // conținutul paginii
// etc...
}
Pentru a fi perfect clari, am putea evita utilizarea funcției query_posts()
și aici și am putea folosi WP_Query
în schimb - astfel:
// ...
global $wp_query;
$wp_query = new WP_Query(array('your query vars here')); // setează noua interogare personalizată ca interogare principală
// bucla pentru articolele de tip custom-post-type aici
wp_reset_query();
// ...
Dar, de ce am face asta când avem la dispoziție o funcție atât de utilă pentru acest scop?

Brian, mulțumesc pentru asta. Am avut dificultăți să fac pre_get_posts să funcționeze pe o pagină în EXACT același scenariu pe care îl descrii: clientul trebuie să adauge câmpuri personalizate/conținut la ceea ce altfel ar fi o pagină de arhivă, deci trebuie creată o "pagină"; clientul trebuie să vadă ceva pentru a adăuga în meniul de navigare, deoarece adăugarea unui link personalizat le scapă; etc. +1 de la mine!

Aceasta poate fi realizată și folosind "pre_get_posts". Am făcut asta pentru a avea o "pagină frontală statică" care afișează tipurile mele de postări personalizate într-o ordine personalizată și cu un filtru personalizat. Această pagină este și paginată. Verifică această întrebare pentru a vedea cum funcționează: http://wordpress.stackexchange.com/questions/30851/how-to-use-a-custom-post-type-archive-as-front-page/30854
Deci, pe scurt, încă nu există un scenariu mai legitim pentru utilizarea query_posts ;)

Pentru că "Trebuie remarcat faptul că utilizarea acesteia pentru a înlocui interogarea principală pe o pagină poate crește timpii de încărcare a paginii, în cele mai rele scenarii dublând sau chiar mai mult cantitatea de muncă necesară. Deși este ușor de utilizat, funcția este, de asemenea, predispusă la confuzii și probleme ulterioare." Sursa http://codex.wordpress.org/Function_Reference/query_posts

Acest răspuns este complet greșit. Poți crea o "Pagină" în WP cu același URL ca și tipul de post personalizat. De exemplu, dacă CPT-ul tău este Banane, poți avea o pagină numită Banane cu același URL. Apoi ai ajunge la siteurl.com/banane. Atâta timp cât ai archive-banane.php în folderul temei tale, atunci va folosi șablonul și va "suprascrie" acea pagină. După cum se menționează într-unul din celelalte comentarii, folosirea acestei "metode" creează de două ori mai multă muncă pentru WP, așadar NU ar trebui folosită niciodată.

Modific interogarea WordPress din functions.php:
//din păcate, condiția "IS_PAGE" nu funcționează în pre_get_posts (este o comportare specifică WORDPRESS)
//deci poți folosi `add_filter('posts_where', ....);` SAU modifică interogarea "PAGE" direct în fișierul template
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%')";
}

Doar pentru a sublinia câteva îmbunătățiri aduse răspunsului acceptat, deoarece WordPress a evoluat de-a lungul timpului și unele lucruri sunt diferite acum (după cinci ani):
pre_get_posts
este un filtru, folosit pentru a modifica orice interogare. Cel mai des este folosit pentru a modifica doar 'interogarea principală':
De fapt, este un action hook. Nu un filtru și va afecta orice interogare.
Interogarea principală apare în șabloanele tale ca:
if( have_posts() ):
while( have_posts() ): the_post();
//Bucla
endwhile;
endif;
De fapt, nici acest lucru nu este adevărat. Funcția have_posts
iterează obiectul global $wp_query
care nu este legat doar de interogarea principală. global $wp_query;
poate fi modificat și cu interogările secundare.
function have_posts() {
global $wp_query;
return $wp_query->have_posts();
}
get_posts()
Aceasta este în esență un wrapper pentru o instanță separată a unui obiect WP_Query.
De fapt, în prezent WP_Query
este o clasă, deci avem o instanță a unei clase.
Concluzie: La momentul în care @StephenHarris a scris, cel mai probabil toate acestea erau adevărate, dar de-a lungul timpului lucrurile în WordPress s-au schimbat.

Tehnic, totul se reduce la filtre în culise, acțiunile sunt doar un filtru simplu. Dar ai dreptate aici, este o acțiune care trece un argument prin referință, ceea ce o diferențiază de acțiunile mai simple.

get_posts
returnează un array de obiecte post, nu un obiect WP_Query
, deci asta este într-adevăr încă corect. și WP_Query
a fost întotdeauna o clasă, instanță a unei clase = obiect.
