Quando usare WP_query(), query_posts() e pre_get_posts
Ho letto @nacin's You don't know Query ieri e sono finito in un vortice di informazioni sulle query. Prima di ieri, stavo (erroneamente) usando query_posts()
per tutte le mie necessità di query. Ora sono un po' più consapevole sull'uso di WP_Query()
, ma ho ancora alcune zone grigie.
Quello che penso di sapere per certo:
Se sto creando loop aggiuntivi in qualsiasi punto della pagina—nella sidebar, nel footer, qualsiasi tipo di "articoli correlati", ecc—dovrei usare WP_Query()
. Posso utilizzarlo ripetutamente in una singola pagina senza alcun danno. (giusto?)
Quello che non so per certo
- Quando dovrei usare @nacin's
pre_get_posts
invece diWP_Query()
? Dovrei usarepre_get_posts
per tutto ora? - Quando voglio modificare il loop in una pagina template — diciamo che voglio modificare una pagina archivio di tassonomia — devo rimuovere la parte
if have_posts : while have_posts : the_post
e scrivere il mioWP_Query()
? Oppure devo modificare l'output usandopre_get_posts
nel mio file functions.php?
tl;dr
Le regole tl;dr che vorrei ricavare da questo sono:
- Non usare mai più
query_posts
- Quando si eseguono query multiple in una singola pagina, usare
WP_Query()
- Quando si modifica un loop, fare questo __________________.
Grazie per qualsiasi consiglio
Terry
ps: Ho visto e letto: Quando dovresti usare WP_Query vs query_posts() vs get_posts()? Che aggiunge un'altra dimensione — get_posts
. Ma non tratta affatto di pre_get_posts
.

Hai ragione nel dire:
Non usare più
query_posts
pre_get_posts
pre_get_posts
è un filtro, per modificare qualsiasi query. È più comunemente usato per alterare solo la 'query principale':
add_action('pre_get_posts','wpse50761_alter_query');
function wpse50761_alter_query($query){
if( $query->is_main_query() ){
//Fai qualcosa alla query principale
}
}
(Consiglio anche di verificare che is_admin()
restituisca false - anche se potrebbe essere ridondante.). La query principale appare nei tuoi template come:
if( have_posts() ):
while( have_posts() ): the_post();
//Il loop
endwhile;
endif;
Se senti il bisogno di modificare questo loop - usa pre_get_posts
. Ovvero, se sei tentato di usare query_posts()
- usa invece pre_get_posts
.
WP_Query
La query principale è un'importante istanza di un oggetto WP_Query
. WordPress lo usa per decidere quale template utilizzare, ad esempio, e tutti gli argomenti passati nell'url (come la paginazione) vengono incanalati in quell'istanza dell'oggetto WP_Query
.
Per loop secondari (ad esempio nelle sidebar, o liste di 'post correlati') vorrai creare una tua istanza separata dell'oggetto WP_Query
. Esempio:
$my_secondary_loop = new WP_Query(...);
if( $my_secondary_loop->have_posts() ):
while( $my_secondary_loop->have_posts() ): $my_secondary_loop->the_post();
//Il loop secondario
endwhile;
endif;
wp_reset_postdata();
Nota wp_reset_postdata();
- questo perché il loop secondario sovrascriverà la variabile globale $post
che identifica il 'post corrente'. Questo essenzialmente la resetta al $post
corrente.
get_posts()
Questo è essenzialmente un wrapper per un'istanza separata di un oggetto WP_Query
. Restituisce un array di oggetti post. I metodi usati nel loop sopra non sono più disponibili. Questo non è un 'Loop', ma semplicemente un array di oggetti 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>
In risposta alle tue domande
- Usa
pre_get_posts
per alterare la tua query principale. Usa un oggettoWP_Query
separato (metodo 2) per loop secondari nelle pagine template. - Se vuoi alterare la query del loop principale, usa
pre_get_posts
.

Quindi c'è qualche scenario in cui sarebbe meglio utilizzare direttamente get_posts() piuttosto che WP_Query?

@drtanz - sì. Per esempio, quando non hai bisogno della paginazione o dei post sticky in cima - in questi casi get_posts()
è più efficiente.

Ma questo non aggiungerebbe una query extra quando potremmo semplicemente modificare pre_get_posts per modificare la query principale?

@drtanz - non useresti get_posts()
per la query principale - è pensato per query secondarie.

Nel tuo esempio con WP_Query, se cambi $my_secondary_loop->the_post(); con $my_secondary_loop->next_post(); puoi evitare di dover ricordare di usare wp_reset_postdata() purché tu usi $my_post per fare ciò che ti serve.

@Privateer Non è così, WP_Query::get_posts()
imposta global $post;

@StephenHarris Ho appena esaminato la classe query e non la vedo. Ho controllato soprattutto perché non uso mai wp_reset_postdata perché faccio sempre le query in questo modo. Stai creando un nuovo oggetto e tutti i risultati sono contenuti al suo interno.

@Privateer - scusa, errore di battitura, WP_Query::the_post()
, vedi: https://github.com/WordPress/WordPress/blob/759f3d894ce7d364cf8bfc755e483ac2a6d85653/wp-includes/query.php#L3732

@StephenHarris Giusto =) Se usi next_post() sull'oggetto invece di usare the_post, non interferisci con la query globale e non devi ricordarti di usare wp_reset_postdata dopo.

@Privateer Ah, mi scuso, sembra che mi sia confuso. Hai ragione (ma non potresti usare alcuna funzione che si riferisca al globale $post
come the_title()
, the_content()
).

@urok93 A volte uso get_posts()
quando ho bisogno di ottenere post correlati ACF, specialmente se ce n'è solo uno. Anche se per standardizzare i miei template sto valutando di riscriverli come istanze di WP_Query.

Esistono due contesti diversi per i loop:
- Il loop principale che viene eseguito in base alla richiesta URL e viene processato prima del caricamento dei template
- I loop secondari che avvengono in qualsiasi altro modo, richiamati dai file template o altrimenti
Il problema con query_posts()
è che è un loop secondario che cerca di essere quello principale e fallisce miseramente. Quindi dimenticate che esista.
Per modificare il loop principale
- Non usare
query_posts()
- Usa il filtro
pre_get_posts
con il controllo$query->is_main_query()
- In alternativa usa il filtro
request
(un po' troppo grezzo quindi l'opzione precedente è migliore)
Per eseguire un loop secondario
Usa new WP_Query
o get_posts()
che sono praticamente intercambiabili (il secondo è un wrapper leggero del primo).
Per ripulire
Usa wp_reset_query()
se hai usato query_posts()
o hai modificato direttamente la variabile globale $wp_query
- quindi quasi mai ne avrai bisogno.
Usa wp_reset_postdata()
se hai usato the_post()
o setup_postdata()
o hai modificato la variabile globale $post
e devi ripristinare lo stato iniziale degli elementi relativi al post.

Ci sono scenari legittimi per l'utilizzo di query_posts($query)
, ad esempio:
Vuoi visualizzare un elenco di articoli o articoli di un custom-post-type su una pagina (utilizzando un page template)
Vuoi far funzionare la paginazione di questi articoli
Ora, perché vorresti visualizzarlo su una pagina invece di utilizzare un template di archivio?
È più intuitivo per un amministratore (il tuo cliente?) - possono vedere la pagina in 'Pagine'
È meglio per aggiungerla ai menu (senza la pagina, dovrebbero aggiungere l'URL direttamente)
Se vuoi visualizzare contenuti aggiuntivi (testo, thumbnail dell'articolo o qualsiasi meta contenuto personalizzato) sul template, puoi ottenerlo facilmente dalla pagina (e ha anche più senso per il cliente). Se utilizzassi un template di archivio, dovresti inserire il contenuto aggiuntivo direttamente nel codice o utilizzare ad esempio le opzioni del tema/plugin (il che lo rende meno intuitivo per il cliente)
Ecco un esempio di codice semplificato (che sarebbe nel tuo page template - ad esempio page-page-of-posts.php):
/**
* Template Name: Pagina di Articoli
*/
while(have_posts()) { // loop principale originale - contenuto della pagina
the_post();
the_title(); // titolo della pagina
the_content(); // contenuto della pagina
// ecc...
}
// ora visualizziamo l'elenco dei nostri articoli di custom-post-type
// prima otteniamo i parametri di paginazione
$paged = 1;
if(get_query_var('paged')) {
$paged = get_query_var('paged');
} elseif(get_query_var('page')) {
$paged = get_query_var('page');
}
// query degli articoli e sostituzione della query principale (pagina) con questa (così la paginazione funziona)
query_posts(array('post_type' => 'my_post_type', 'post_status' => 'publish', 'paged' => $paged));
// paginazione
next_posts_link();
previous_posts_link();
// loop
while(have_posts()) {
the_post();
the_title(); // titolo del tuo articolo di custom-post-type
the_content(); // contenuto del tuo articolo di custom-post-type
}
wp_reset_query(); // reimposta la query principale (globale $wp_query) alla query originale della pagina (la ottiene dalla variabile globale $wp_the_query) e reimposta i dati dell'articolo
// Quindi, ora possiamo visualizzare nuovamente il contenuto relativo alla pagina (se lo desideriamo)
while(have_posts()) { // loop principale originale - contenuto della pagina
the_post();
the_title(); // titolo della pagina
the_content(); // contenuto della pagina
// ecc...
}
Ora, per essere perfettamente chiari, potremmo evitare di usare query_posts()
anche qui e usare invece WP_Query
- in questo modo:
// ...
global $wp_query;
$wp_query = new WP_Query(array('i tuoi parametri di query qui')); // imposta la nuova query personalizzata come query principale
// il tuo loop di custom-post-type qui
wp_reset_query();
// ...
Ma, perché dovremmo farlo quando abbiamo a disposizione una così comoda piccola funzione?

Brian, grazie per questo. Ho avuto difficoltà a far funzionare pre_get_posts su una pagina nello SCENARIO ESATTO che descrivi: il cliente ha bisogno di aggiungere campi personalizzati/contenuto a quella che altrimenti sarebbe una pagina di archivio, quindi deve essere creata una "pagina"; il cliente ha bisogno di vedere qualcosa da aggiungere al menu di navigazione, poiché aggiungere un link personalizzato è troppo complicato per loro; ecc. +1 da parte mia!

Questo può essere fatto anche utilizzando "pre_get_posts". L'ho fatto per avere una "pagina frontale statica" che elenca i miei custom post type in un ordine personalizzato e con un filtro personalizzato. Questa pagina è anche impaginata. Dai un'occhiata a questa domanda per vedere come funziona: http://wordpress.stackexchange.com/questions/30851/how-to-use-a-custom-post-type-archive-as-front-page/30854
Quindi, in breve, non c'è ancora uno scenario legittimo per usare query_posts ;)

Perché "Va notato che usare questa funzione per sostituire la query principale su una pagina può aumentare i tempi di caricamento, negli scenari peggiori più che raddoppiando la quantità di lavoro necessaria o anche di più. Sebbene sia facile da usare, la funzione è anche incline a confusione e problemi successivi." Fonte http://codex.wordpress.org/Function_Reference/query_posts

Questa risposta è completamente sbagliata. Puoi creare una "Pagina" in WP con lo stesso URL del Custom post type. Ad esempio, se il tuo CPT si chiama Banane, puoi ottenere una pagina chiamata Banane con lo stesso URL. Quindi finiresti con siteurl.com/banane. Fintanto che hai archive-banane.php nella cartella del tuo tema, allora utilizzerà il template e "sovrascriverà" quella pagina invece. Come affermato in uno degli altri commenti, utilizzare questo "metodo" crea il doppio del lavoro per WP, quindi NON dovrebbe MAI essere usato.

Modifico la query di WordPress da functions.php:
//sfortunatamente, la condizione "IS_PAGE" non funziona in pre_get_posts (è un comportamento di WORDPRESS)
//quindi puoi usare `add_filter('posts_where', ....);` OPPURE modificare direttamente la query "PAGE" nel file 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%')";
}

Solo per delineare alcuni miglioramenti alla risposta accettata, dato che WordPress si è evoluto nel tempo e alcune cose sono diverse ora (cinque anni dopo):
pre_get_posts
è un filtro, per modificare qualsiasi query. Viene spesso utilizzato per modificare solo la 'query principale':
In realtà è un action hook. Non un filtro, e influenzerà qualsiasi query.
La query principale appare nei tuoi template come:
if( have_posts() ):
while( have_posts() ): the_post();
//Il loop
endwhile;
endif;
Anche questo non è del tutto vero. La funzione have_posts
itera sull'oggetto global $wp_query
che non è relativo solo alla query principale. global $wp_query;
può essere modificato anche con query secondarie.
function have_posts() {
global $wp_query;
return $wp_query->have_posts();
}
get_posts()
Questo è essenzialmente un wrapper per un'istanza separata di un oggetto WP_Query.
In realtà, al giorno d'oggi WP_Query
è una classe, quindi abbiamo un'istanza di una classe.
Per concludere: Al tempo in cui @StephenHarris ha scritto molto probabilmente tutto questo era vero, ma nel tempo le cose in WordPress sono cambiate.

Tecnicamente, sono tutti filtri sotto il cofano, le azioni sono semplicemente un filtro basilare. Ma hai ragione qui, è un'azione che passa un argomento per riferimento, ed è così che differisce dalle azioni più semplici.

get_posts
restituisce un array di oggetti post, non un oggetto WP_Query
, quindi è ancora corretto. E WP_Query
è sempre stata una classe, un'istanza di una classe = oggetto.
