Când să folosești WP_query(), query_posts() și pre_get_posts

1 mai 2012, 16:04:08
Vizualizări: 201K
Voturi: 179

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

  1. Când folosesc @nacin's pre_get_posts versus WP_Query()? Ar trebui să folosesc pre_get_posts pentru tot acum?
  2. 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 propriul WP_Query()? Sau modific output-ul folosind pre_get_posts în fișierul functions.php?

tl;dr

Regulile tl;dr pe care aș vrea să le extrag din asta sunt:

  1. Nu mai folosiți niciodată query_posts
  2. Când rulați interogări multiple pe o singură pagină, folosiți WP_Query()
  3. 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.

2
Comentarii

@saltcod, acum este diferit, WordPress a evoluat, am adăugat câteva comentarii în comparație cu răspunsul acceptat aici.

prosti prosti
28 dec. 2016 03:46:02
Toate răspunsurile la întrebare 5
13
164

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ă

  1. Folosiți pre_get_posts pentru a modifica interogarea principală. Folosiți un obiect WP_Query separat (metoda 2) pentru buclele secundare în paginile de șabloane.
  2. Dacă doriți să modificați interogarea buclei principale, folosiți pre_get_posts.
1 mai 2012 16:27:31
Comentarii

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

urok93 urok93
25 aug. 2012 19:09:40

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

Stephen Harris Stephen Harris
25 aug. 2012 21:00:48

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

urok93 urok93
26 aug. 2012 23:54:06

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

Stephen Harris Stephen Harris
27 aug. 2012 01:42:19

Î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 Privateer
19 sept. 2015 01:02:58

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

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

@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 Privateer
19 sept. 2015 16:14:15

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

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

@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 Privateer
19 sept. 2015 21:01:23

@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()).

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

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

Privateer Privateer
24 sept. 2015 19:34:53

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

Slam Slam
14 feb. 2018 02:15:49

@urok93 Eu folosesc aproape întotdeauna get_posts pentru că este fără stare (stateless), în timp ce WP_Query interferează cu cine știe ce variabile globale.

tklodd tklodd
12 ian. 2022 01:56:53
Arată celelalte 8 comentarii
1
65

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.

1 mai 2012 16:27:52
Comentarii

Rarst se referea la wp_reset_postdata()

Gregory Gregory
1 iun. 2012 12:18:31
4
26

Există scenarii legitime pentru utilizarea funcției query_posts($query), de exemplu:

  1. 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ă)

  2. 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ă?

  1. Este mai intuitiv pentru un administrator (clientul dvs.) - pot vedea pagina în lista 'Pagini'

  2. Este mai ușor de adăugat în meniuri (fără pagina, ar trebui să adauge URL-ul direct)

  3. 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?

16 sept. 2012 10:34:09
Comentarii

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!

Will Lanni Will Lanni
13 dec. 2012 13:07:49

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

2ndkauboy 2ndkauboy
12 ian. 2015 16:47:27

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

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

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

Hybrid Web Dev Hybrid Web Dev
19 iun. 2015 23:07:03
1
11

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

aș fi interesat să văd acest exemplu, dar cu clauza WHERE pe un meta personalizat.

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

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.

28 dec. 2016 03:26:41
Comentarii

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.

Milo Milo
28 dec. 2016 04:16:33

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.

Milo Milo
28 dec. 2016 04:16:49

Mulțumesc, @Milo, corect, din nu știu ce motiv aveam un model prea simplificat în cap.

prosti prosti
28 dec. 2016 10:51:18