Когда использовать WP_query (), query_posts () и pre_get_posts


159

Я прочитал @ nacin's. Вы не знаете Query вчера, и меня отправили в какую-то дырку с кроликом. До вчерашнего дня я (неправильно) использовал query_posts()для всех моих запросов. Теперь я немного мудрее в использовании WP_Query(), но у меня все еще есть серые области.

То, что я думаю, я знаю наверняка:

Если я создаю дополнительные циклы в любом месте на странице - на боковой панели, в нижнем колонтитуле, любых видах «связанных сообщений» и т. Д. - я хочу использовать WP_Query(). Я могу использовать это неоднократно на одной странице без какого-либо вреда. (правильно?).

Что я точно не знаю

  1. Когда я использую @ nacin в pre_get_posts VS. WP_Query()? Должен ли я использовать pre_get_postsдля всего сейчас?
  2. Когда я хочу изменить цикл на странице шаблона, скажем, хочу изменить страницу архива таксономии, удалить if have_posts : while have_posts : the_postчасть и написать свою собственную WP_Query()? Или мне изменить вывод, используя pre_get_postsв моем файле functions.php?

ТЛ; др

Правила tl; dr, которые я хотел бы извлечь из этого:

  1. Никогда не используйте query_postsбольше
  2. При выполнении нескольких запросов на одной странице используйте WP_Query()
  3. При изменении цикла сделайте это __________________.

Спасибо за любую мудрость

Терри

PS: я видел и читал: когда вы должны использовать WP_Query против query_posts () против get_posts ()? Который добавляет другое измерение - get_posts. Но не имеет дело pre_get_postsвообще.



@saltcod, теперь другой, WordPress развивался, я добавил несколько комментариев по сравнению с принятым ответом здесь .
прост

Ответы:


145

Вы правы сказать:

Никогда не используйте query_postsбольше

pre_get_posts

pre_get_postsэто фильтр, для изменения любого запроса. Чаще всего он используется для изменения только «основного запроса»:

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

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

(Я бы также проверил, что is_admin()возвращает false - хотя это может быть избыточно.). Основной запрос появляется в ваших шаблонах как:

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

Если вам когда-нибудь понадобится редактировать этот цикл - используйте pre_get_posts. т.е. если вы склонны использовать query_posts()- используйте pre_get_postsвместо этого.

WP_Query

Основной запрос является важным примером WP_Query object. WordPress использует его, чтобы решить, например, какой шаблон использовать, и все аргументы, передаваемые в URL (например, разбиение на страницы), все направляются в этот экземпляр WP_Queryобъекта.

Для вторичных циклов (например, в боковых панелях или списках «связанных сообщений») вы захотите создать свой отдельный экземпляр WP_Queryобъекта. Например

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

Обратите внимание wp_reset_postdata();- это потому, что вторичный цикл переопределит глобальную $postпеременную, которая идентифицирует «текущий пост». Это по существу сбрасывает это на $postмы.

get_posts ()

По сути, это оболочка для отдельного экземпляра WP_Queryобъекта. Это возвращает массив почтовых объектов. Методы, использованные в приведенном выше цикле, больше не доступны для вас. Это не «цикл», просто массив объектов 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>

В ответ на ваши вопросы

  1. Используйте, pre_get_postsчтобы изменить ваш основной запрос. Используйте отдельный WP_Queryобъект (метод 2) для вторичных циклов на страницах шаблона.
  2. Если вы хотите изменить запрос основного цикла, используйте pre_get_posts.

Так есть ли сценарий, когда можно было бы перейти прямо к get_posts (), а не к WP_Query?
urok93

@drtanz - да. Скажем, например, вам не нужны нумерация страниц или липкие посты вверху - в этих случаях get_posts()это более эффективно.
Стивен Харрис

Но разве это не добавило бы дополнительный запрос, где мы могли бы просто изменить pre_get_posts, чтобы изменить основной запрос?
urok93

@drtanz - вы не будете использовать get_posts()для основного запроса - для вторичных запросов.
Стивен Харрис

1
@StephenHarris Right =) Если вы используете next_post () на объекте вместо the_post, вы не наступаете на глобальный запрос и не должны забывать использовать wp_reset_postdata впоследствии.
Капер

55

Есть два разных контекста для циклов:

  • основной цикл, который происходит на основе URL-запроса и обрабатывается до загрузки шаблонов
  • вторичные циклы, которые происходят любым другим способом, вызывается из файлов шаблона или иным образом

Проблема в query_posts()том, что это вторичный цикл, который пытается быть основным и с треском проваливается. Таким образом, забудьте, что он существует.

Чтобы изменить основной цикл

  • не использовать query_posts()
  • использовать pre_get_postsфильтр с $query->is_main_query()проверкой
  • поочередно используйте requestфильтр (немного слишком грубый, так что выше лучше)

Запустить вторичный цикл

Используйте new WP_Queryили get_posts()которые в значительной степени взаимозаменяемы (последний является тонкой оберткой для первого).

Помыть

Используйте, wp_reset_query()если вы использовали query_posts()или перепутали с global $wp_queryнапрямую - так вам почти никогда не понадобится.

Используйте, wp_reset_postdata()если вы использовали the_post()или setup_postdata()или перепутали с глобальным $postи нужно восстановить начальное состояние пост-связанных вещей.


3
Первый раз имел в видуwp_reset_postdata()
Грегори

23

Существуют законные сценарии использования query_posts($query), например:

  1. Вы хотите отобразить список сообщений или сообщений пользовательского типа на странице (используя шаблон страницы)

  2. Вы хотите, чтобы эти посты работали

Теперь, почему вы хотите отобразить его на странице вместо использования шаблона архива?

  1. Это более интуитивно понятно для администратора (вашего клиента?) - они могут видеть страницу в «Страницах»

  2. Это лучше для добавления его в меню (без страницы, они должны были бы добавить URL непосредственно)

  3. Если вы хотите отобразить дополнительный контент (текст, миниатюру поста или любой другой мета контент) в шаблоне, вы можете легко получить его со страницы (и все это имеет больше смысла и для клиента). Посмотрите, использовали ли вы шаблон архива, вам нужно либо жестко закодировать дополнительный контент, либо использовать, например, параметры темы / плагина (что делает его менее понятным для клиента)

Вот упрощенный пример кода (который будет на вашем шаблоне страницы - например, 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...
}

Теперь, чтобы быть совершенно ясным, мы могли бы избегать использования query_posts()здесь и использовать WP_Queryвместо этого - вот так:

// ...

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

// ...

Но зачем нам это делать, когда у нас есть такая приятная маленькая функция?


1
Брайан, спасибо за это. Я изо всех сил пытался заставить pre_get_posts работать на странице в ТОЧНО описанном вами сценарии: клиент должен добавить пользовательские поля / содержимое к тому, что в противном случае было бы страницей архива, поэтому необходимо создать «страницу»; клиент должен видеть что-то, что нужно добавить в навигационное меню, поскольку добавление пользовательской ссылки ускользает от них; и т. д. +1 от меня!
Будет Ланни

2
Это также можно сделать с помощью «pre_get_posts». Я сделал это, чтобы «статическая титульная страница» перечисляла мои собственные типы записей в произвольном порядке и с пользовательским фильтром. Эта страница также разбита на страницы. Проверьте этот вопрос, чтобы увидеть, как он работает: wordpress.stackexchange.com/questions/30851/… Короче говоря, нет более законного сценария использования query_posts;)
2ndkauboy

1
Потому что «Следует отметить, что использование этого для замены основного запроса на странице может увеличить время загрузки страницы, в худшем случае более, чем удвоение объема необходимой работы или более. Хотя эта функция проста в использовании, она также подвержена путанице и проблемы позже. " Исходный кодx.wordpress.org/Function_Reference/query_posts
Крянгэ

Этот ответ неверен. Вы можете создать «страницу» в WP с тем же URL-адресом, что и пользовательский тип публикации. Например, если ваш CPT - «Бананы», вы можете получить страницу с именем «Бананы» с тем же URL. Тогда вы в конечном итоге с siteurl.com/bananas. Пока у вас есть архив-bananas.php в папке вашей темы, он будет использовать шаблон и «переопределить» эту страницу. Как указано в одном из других комментариев, использование этого «метода» создает двойную рабочую нагрузку для WP, поэтому его НЕ следует использовать.
Hybrid Web Dev

8

Я изменяю запрос WordPress из 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%')"; 
}

было бы интересно увидеть этот пример, но где предложение находится на пользовательских мета.
Эндрю Уэлч

6

Просто чтобы наметить некоторые улучшения в принятом ответе, так как WordPress развивался с течением времени, и некоторые вещи изменились (пять лет спустя):

pre_get_postsэто фильтр, для изменения любого запроса. Чаще всего он используется для изменения только «основного запроса»:

На самом деле это хук действия. Не фильтр, и это повлияет на любой запрос.

Основной запрос появляется в ваших шаблонах как:

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

На самом деле, это тоже не так. Функция have_postsвыполняет итерацию global $wp_queryобъекта, который не связан только с основным запросом. global $wp_query;может быть изменен и с помощью вторичных запросов.

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

get_posts ()

По сути, это оболочка для отдельного экземпляра объекта WP_Query.

На самом деле, в настоящее время WP_Queryэто класс, поэтому у нас есть экземпляр класса.


В заключение: в то время, когда @StephenHarris писал, скорее всего, все это было правдой, но со временем все изменилось в WordPress.


Технически это все фильтры под капотом, действия - это просто фильтр. Но вы правы здесь, это действие, которое передает аргумент по ссылке, чем он отличается от более простых действий.
Майло

get_postsвозвращает массив объектов post, а не WP_Queryобъекта, так что это действительно правильно. и WP_Queryвсегда был классом, экземпляром класса = объекта.
Майло

Спасибо, @Milo, верно, почему-то у меня в голове была упрощенная модель.
прост
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.