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

1 мая 2012 г., 16:04:08
Просмотры: 201K
Голосов: 179

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

Что, как мне кажется, я знаю наверняка:

Если я создаю дополнительные циклы где-либо на странице — в сайдбаре, в футере, любые "похожие записи" и т.д. — я должен использовать WP_Query(). Я могу использовать его многократно на одной странице без вреда (верно?).

Что я не знаю наверняка

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

кратко

Краткие правила, которые я хотел бы вывести из этого:

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

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

Терри

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

2
Комментарии

Возможный дубликат вопроса Когда следует использовать WP_Query вместо query_posts() или get_posts()?

dotancohen dotancohen
3 янв. 2016 г. 12:29:13

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

prosti prosti
28 дек. 2016 г. 03:46:02
Все ответы на вопрос 5
13
164

Вы совершенно правы, говоря:

Никогда больше не используйте 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() ){
        //Делаем что-то с главным запросом
      }
}

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

if( have_posts() ):
    while( have_posts() ): the_post();
       //Цикл
    endwhile;
endif;

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

WP_Query

Главный запрос — это важный экземпляр объекта WP_Query. 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();
       //Дополнительный цикл
    endwhile;
endif;
wp_reset_postdata();

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

get_posts()

По сути, это обёртка для отдельного экземпляра объекта WP_Query. Она возвращает массив объектов записей. Методы, используемые в цикле выше, больше не доступны. Это не «цикл», а просто массив объектов записей.

<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.
1 мая 2012 г. 16:27:31
Комментарии

Есть ли сценарии, когда лучше сразу использовать get_posts() вместо WP_Query?

urok93 urok93
25 авг. 2012 г. 19:09:40

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

Stephen Harris Stephen Harris
25 авг. 2012 г. 21:00:48

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

urok93 urok93
26 авг. 2012 г. 23:54:06

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

Stephen Harris Stephen Harris
27 авг. 2012 г. 01:42:19

В вашем примере с WP_Query, если вы замените $my_secondary_loop->the_post(); на $my_post = $my_secondary_loop->next_post();, то можете избежать необходимости помнить о wp_reset_postdata(), при условии что вы используете $my_post для выполнения нужных действий.

Privateer Privateer
19 сент. 2015 г. 01:02:58

@Privateer Это не так, WP_Query::get_posts() устанавливает global $post;

Stephen Harris Stephen Harris
19 сент. 2015 г. 11:48:05

@StephenHarris Я только что просмотрел класс запроса и не нашел этого. Проверил в основном потому, что никогда не использую wp_reset_postdata, так как всегда делаю запросы таким способом. Вы создаете новый объект, и все результаты содержатся внутри него.

Privateer Privateer
19 сент. 2015 г. 16:14:15

@Privateer - извините, опечатка, WP_Query::the_post(), см.: https://github.com/WordPress/WordPress/blob/759f3d894ce7d364cf8bfc755e483ac2a6d85653/wp-includes/query.php#L3732

Stephen Harris Stephen Harris
19 сент. 2015 г. 17:39:19

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

Privateer Privateer
19 сент. 2015 г. 21:01:23

@Privateer Ох, мои извинения, кажется, я сам себя запутал. Ты прав (но ты не сможешь использовать функции, которые ссылаются на глобальную переменную $post, например the_title(), the_content()).

Stephen Harris Stephen Harris
23 сент. 2015 г. 18:31:04

Верно =) Я никогда не использую эти функции, поэтому мне их и не хватает.

Privateer Privateer
24 сент. 2015 г. 19:34:53

@urok93 Иногда я использую get_posts(), когда мне нужно получить связанные записи через ACF, особенно если там только одна запись. Хотя для стандартизации шаблонов я рассматриваю возможность переписать их как экземпляры WP_Query.

Slam Slam
14 февр. 2018 г. 02:15:49

@urok93 Я почти всегда использую get_posts, потому что это состояние не сохраняется, в то время как WP_Query вмешивается в неизвестно какие глобальные переменные.

tklodd tklodd
12 янв. 2022 г. 01:56:53
Показать остальные 8 комментариев
1
65

Существует два различных контекста для циклов:

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

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

Как изменить основной цикл

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

Как выполнить вторичный цикл

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

Как очистить

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

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

1 мая 2012 г. 16:27:52
Комментарии

Rarst имел в виду wp_reset_postdata()

Gregory Gregory
1 июн. 2012 г. 12:18:31
4
26

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

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

  2. Вы хотите, чтобы работала пагинация этих записей

Теперь, зачем вам может понадобиться отображать это на странице вместо использования шаблона архива?

  1. Это более интуитивно понятно для администратора (вашего клиента?) — они могут видеть страницу в разделе 'Страницы'

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

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

Вот упрощенный пример кода (который будет в вашем шаблоне страницы — например, page-page-of-posts.php):

/**
 * Название шаблона: Страница с записями
 */

while(have_posts()) { // оригинальный основной цикл - контент страницы
  the_post();
  the_title(); // заголовок страницы
  the_content(); // контент страницы
  // и т.д...
}

// теперь отображаем список записей нашего пользовательского типа поста

// сначала получаем параметры пагинации
$paged = 1;
if(get_query_var('paged')) {
  $paged = get_query_var('paged');
} elseif(get_query_var('page')) {
  $paged = get_query_var('page');
}

// запрашиваем записи и заменяем основной запрос (страницы) этим (чтобы пагинация работала)
query_posts(array('post_type' => 'my_post_type', 'post_status' => 'publish', 'paged' => $paged));

// пагинация
next_posts_link();
previous_posts_link();

// цикл
while(have_posts()) {
  the_post();
  the_title(); // заголовок вашей записи пользовательского типа поста
  the_content(); // контент вашей записи пользовательского типа поста
}

wp_reset_query(); // устанавливает основной запрос (глобальная $wp_query) в исходный запрос страницы (он получает его из глобальной переменной $wp_the_query) и сбрасывает данные поста

// Теперь мы можем снова отобразить контент, связанный со страницей (если захотим)
while(have_posts()) { // оригинальный основной цикл - контент страницы
  the_post();
  the_title(); // заголовок страницы
  the_content(); // контент страницы
  // и т.д...
}

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

// ...

global $wp_query;
$wp_query = new WP_Query(array('ваши параметры запроса здесь')); // устанавливает новый пользовательский запрос как основной

// ваш цикл для пользовательского типа поста здесь

wp_reset_query();

// ...

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

16 сент. 2012 г. 10:34:09
Комментарии

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

Will Lanni Will Lanni
13 дек. 2012 г. 13:07:49

Это также можно сделать с помощью "pre_get_posts". Я использовал это для создания "статической главной страницы", которая выводит мои пользовательские типы записей в произвольном порядке и с произвольным фильтром. Эта страница также имеет пагинацию. Посмотрите этот вопрос, чтобы понять, как это работает: http://wordpress.stackexchange.com/questions/30851/how-to-use-a-custom-post-type-archive-as-front-page/30854

Короче говоря, по-прежнему нет более законного сценария для использования query_posts ;)

2ndkauboy 2ndkauboy
12 янв. 2015 г. 16:47:27

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

Claudiu Creanga Claudiu Creanga
9 мар. 2015 г. 18:31:50

Этот ответ совершенно неверен. Вы можете создать "Страницу" в WordPress с таким же URL, как у пользовательского типа записи. Например, если ваш CPT называется Bananas, вы можете создать страницу с именем Bananas и тем же URL. Тогда у вас получится siteurl.com/bananas. Если у вас в папке темы есть файл archive-bananas.php, то он будет использоваться как шаблон и "переопределит" эту страницу. Как указано в одном из других комментариев, использование этого "метода" создает двойную нагрузку на WordPress, поэтому его НИКОГДА не следует использовать.

Hybrid Web Dev Hybrid Web Dev
19 июн. 2015 г. 23:07:03
1
11

Я изменяю запрос WordPress из файла functions.php:

//К сожалению, условие "IS_PAGE" не работает в pre_get_posts (это поведение WORDPRESS)
//поэтому вы можете использовать `add_filter('posts_where', ....);` ИЛИ изменить запрос "PAGE" непосредственно в файле шаблона

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 янв. 2015 г. 12:11:29
Комментарии

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

Andrew Welch Andrew Welch
3 мар. 2017 г. 18:20:07
3

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

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

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

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

if( have_posts() ):
    while( have_posts() ): the_post();
       //Цикл
    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 произошли изменения.

28 дек. 2016 г. 03:26:41
Комментарии

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

Milo Milo
28 дек. 2016 г. 04:16:33

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

Milo Milo
28 дек. 2016 г. 04:16:49

Спасибо, @Milo, верно, по какой-то причине у меня в голове была чрезмерно упрощённая модель.

prosti prosti
28 дек. 2016 г. 10:51:18