Если вы разрабатываете темы и плагины для WordPress, то вы наверняка встречались с функцией query_posts()
в многочисленных примерах и уроках в сети. В этой статье мы расскажем почему вам не следует никогда использовать эту функцию.
Основные и вторичные запросы
Основной запрос (или основной цикл) в WordPress это тот, который выполняется на раннем этапе загрузки ядра, он строится из запрошенного URL, настроек постоянных ссылок и т.д. Во время основного запроса WordPress определяет такие параметры как количество записей на страницу, используемый шаблон в теме и прочие. Основной запрос делает сам WordPress.
Вторичный запрос это тот, который выполняется дополнительно к основному. Например:
- Вывести в боковой колонке список самых популярных записей
- Вывести выделенные записи в слайдере на главной странице
- Вывести записи из той же категории в блоке «похожие записи»
Вторичные запросы выполняются с помощью класса WP_Query
, или с помощью одной из вспомогательных функций get_posts()
, query_posts()
и т.д. Если вы не знакомы с WP_Query
, советуем прочитать нашу статью.
Основным отличием query_posts()
от других вспомогательных функций является то, что query_posts()
заменяет основной цикл на новый, вторичный цикл WordPress. Это значит, что в цикле с query_posts()
мы можем работать так же, как и в обычном основном цикле.
Для сравнения рассмотрим цикл для вывода популярных записей с помощью WP_Query
:
// Вторичный цикл $popular = new WP_Query( ... ); while ( $popular->have_posts() ) { $popular->the_post(); the_title(); // вывести название the_content(); // вывести содержимое }
Этот же цикл с помощью query_posts()
:
// Вторичный цикл query_posts( ... ); while ( have_posts() ) { the_post(); the_title(); // вывести название the_content(); // вывести содержимое }
Безусловно второй вариант выглядит немного чище и привычнее, поскольку такая конструкция чаще всего встречается при работе с основным циклом WordPress.
Именно поэтому разработчики часто думают, что query_posts()
изменяет основной запрос WordPress, но это не так. Функция query_posts()
заменяет основной цикл новым вторичным циклом, и происходит это после выполнения основного запроса.
$wp_query и $wp_the_query
После выполнения основного запроса WordPress помещает его результат в глобальную переменную $wp_the_query
, а в глобальной переменной $wp_query
хранится всего лишь ссылка на $wp_the_query
. Функции have_posts()
, the_post()
и прочие работают именно с глобальным объектом $wp_query
.
Чтобы в этом убедиться достаточно взглянуть на реализацию подобных функций:
function have_posts() { global $wp_query; return $wp_query->have_posts(); }
Функция query_posts()
создает новый вторичный запрос с помощью WP_Query
и помещает результат в эту же глобальную переменную $wp_query
:
function query_posts( $query ) { $GLOBALS['wp_query'] = new WP_Query(); return $GLOBALS['wp_query']->query( $query ); }
Таким образом функции, которые предназначены для работы с основным циклом WordPress начинают работать с нашим вторичным запросом, а основной запрос остался в глобальной переменной $wp_the_query
, ссылку на которую можно восстановить с помощью функции wp_reset_query()
.
function wp_reset_query() { $GLOBALS['wp_query'] = $GLOBALS['wp_the_query']; wp_reset_postdata(); }
После этого, функции have_posts()
и другие вновь работают с основным циклом WordPress, но разработчики часто об этом забывают, в результате чего перестает работать пагинация, некоторые виджеты и прочее.
Пагинация
Самым частым результатом использования query_posts()
является сломанная пагинация, когда например первые две страницы работают, а третья и четвертая возвращают ошибку 404. Давайте рассмотрим как, и почему это происходит.
По умолчанию WordPress показывает десять записей на одной странице. Допустим у нас всего двадцать записей, это всего две страницы. Изменить количество записей на страницу можно легко с помощью query_posts()
в начале нашего шаблона index.php или archive.php:
global $query_string; query_posts( $query_string . '&posts_per_page=5' );
Таким образом на каждой странице у нас будет пять записей, а не десять, а наш плагин для пагинации будет отображать четыре страницы вместо двух. Все сходится, но при переходе на третью страницу мы получаем ошибку 404. Почему это происходит?
Напоминаем, что основной запрос WordPress происходит еще до того, как обрабатываются шаблоны index.php или archive.php, где происходит наша «подмена». В основном запросе количество записей на страницу — десять, и всего две страницы. Третей и четвертой страниц в основном запросе нет.
Именно основной запрос определяет какой шаблон темы будет использоваться, и при запросе третей или четвертой страницы WordPress будет использовать шаблон 404.php.
Изменение количества записей на страницу это самый простой и явный пример ошибок с query_posts()
. Гораздо сложнее подобные ошибки отловить, если вы например исключаете метку или категорию из списка записей на главной, или добавляете произвольный тип записей в поток.
Событие pre_get_posts
Наиболее правильным способом изменить основной цикл WordPress является событие pre_get_posts
, которое происходит перед каждым запросом WP_Query
. Работать с этим событием можно в плагине или в файле functions.php вашей темы:
function my_pre_get_posts( $query ) { if ( ! is_admin() && $query->is_main_query() ) { $query->set( 'posts_per_page', 5 ); } } add_action( 'pre_get_posts', 'my_pre_get_posts' );
Важно отметить, что pre_get_posts
вызывается для каждого запроса WP_Query
, включая основной запрос, навигационное меню, вторичные запросы в виджетах и прочее. С помощью метода is_main_query()
мы изменяем параметры только основного запроса, а с помощью проверки is_admin()
мы меняем запрос только на лицевой части сайта и не в административной панели.
Результат такого подхода тот же, что и с query_posts()
, но в этот раз будет работать пагинация, а загрузка страницы будет происходить быстрее, поскольку с pre_get_posts
мы действительно изменили основной запрос перед его выполнением и не выполняли вторичных запросов.
Альтернативы query_posts()
Если вам необходимо выполнить вторичный запрос в WordPress, воспользуйтесь функцией get_posts()
или конструкцией new WP_Query()
. При работе с ними ваш код будет более явным и понятным для читающих.
Когда вам необходимо изменить основной запрос WordPress перед его выполнением, самым простым способом является событие pre_get_posts
, или фильтр request
, который выполняется еще раньше, чем pre_get_posts
и только для основного запроса.
Если у вас возникли вопросы про query_posts()
или WP_Query
, оставьте комментарий и мы обязательно вам ответим.