ГлавнаяРазноеОсновы WP_Query в WordPress

Основы WP_Query в WordPress

WP_Query — это один из самых важных API в WordPress, ведь именно с помощью WP_Query мы имеем возможность получать записи, страницы и произвольные типы из базы данных. В этой статье мы рассмотрим основные принципы работы с классом WP_Query и примеры его использования в плагинах и темах.

Что такое WP_Query

WP_Query — это класс, который позволяет разработчикам получать доступ к записям, страницам и произвольным типам данных в WordPress. При этом разработчикам не нужно самостоятельно писать сложные SQL запросы в базу данных, ведь WP_Query сделает это всё за нас.

Ниже приведён самый простой запрос с помощью класса WP_Query:

$query = new WP_Query( array( 'category_name' => 'news' ) );

Таким образом WordPress сделает запрос в базу данных, чтобы получить наши записи из категории «news». Результаты запроса будут храниться в объекте $query, с которым мы будем работать с помощью специальных методов, наверняка знакомых вам:

while ( $query->have_posts() ) :
    $query->the_post();

    the_title(); // вывести название статьи
    the_content(); // вывести содержание
endwhile;

С помощью метода have_posts() мы проверяем есть ли записи в буфере объекта $query, а с помощью метода the_post() мы берём одну запись из буфера и готовим её к выводу, делая доступными привычные нам функции the_title(), the_content() и другие.

После итерации над каждым объектом в буфере записей $query, метод have_posts() вернёт значение false и выйдет из нашего цикла while. Таким образом мы выведем заголовок и содержимое каждой статьи, полученной с помощью WP_Query.

Параметры WP_Query

Аргументом к запросу WP_Query может быть массив содержащий параметры запроса:

$query = new WP_Query( array(
    'category_name' => 'news',
    'posts_per_page' => 5,
    'tag' => 'finance',
) );

Или аналогичной строкой:

$query = new WP_Query( 'category_name=news&posts_per_page=5&tag=finance' );

Мы рекомендуем использовать массив, так как его проще расположить на более чем одной строке, и соответственно легче читать и изменять при сложных запросах.

Существует огромное количество допустимых параметров в запросах WP_Query: категории, метки, типы записей, даты, авторы, таксономия, мета-данные и многое другое. Здесь мы попытаемся рассказать о самых важных и часто используемых параметрах.

Категории (рубрики)

Параметры категорий позволяют получить записи входящие (или не входящие) в определённую категорию или ряд категорий:

  • cat — число идентификатор определённой категории
  • category_name — название определённой категории (ярлык)
  • category__and — получить только те записи, которые присутствуют во всех категориях заданных данным массивом
  • category__in — получить записи, которые присутствуют хотя бы в одной из категорий заданных массивом
  • category__not_in — получить записи, которые не входят ни в одну из категорий заданных массивом

Например получить записи из категории с ярлыком (slug) «news»:

$query = new WP_Query( array(
    'category_name' => 'news',
) );

Получить записи, входящие в одну из категорий с идентификатором 10, 11 или 12. Получить идентификатор категории, имея её ярлык или название, можно с помощью функции get_term_by().

$query = new WP_Query( array(
    'category__in' => array( 10, 11, 12 ),
) );

Получить записи, входящие в одну из категорий с идентификатором 10, 11 или 12, но не входящие ни в одну из категорий с идентификатором 21 или 22:

$query = new WP_Query( array(
    'category__in' => array( 10, 11, 12 ),
    'category__not_in' => array( 21, 22 ),
) );

Метки

Работа с параметрами меток (или тегов) в WP_Query мало чем отличается от категорий:

  • tag — получить записи с указанной меткой (ярлык)
  • tag_id — получить записи с указанной меткой с помощью идентификатора
  • tag__and — получить записи имеющие все указанные метки в массиве (идентификаторы)
  • tag__in — получить записи имеющие хотя бы одну метку из массива (идентификаторы)
  • tag__not_in — получить записи не имеющие ни одной из меток в массиве (идентификаторы)
  • tag_slug__and — получить записи имеющие все метки указанные в массиве (ярлыки)
  • tag_slug__in — получить записи имеющие хотя бы одну метку из указанного массива (ярлыки)

Например, получить записи имеющие метки с идентификаторами 11, 12 и 13, но исключить записи, которые имеют метки с идентификаторами 21, 22 или 23. Так же как и с категориями, получить идентификатор метки из её названия можно с помощью функции get_term_by().

$query = new WP_Query( array(
    'tag__and' => array( 11, 12, 13 ),
    'tag__not_in' => array( 21, 22, 23 ),
) );

Записи, страницы и произвольные типы данных

С помощью WP_Query вы можете делать запросы на определённые записи, страницы и типы данных:

  • post_type — тип записи, по умолчанию post
  • post_status — статус записи, по умолчанию publish
  • p — идентификатор записи
  • page_id — идентификатор страницы
  • name — получить запись по её названию
  • pagename — получить страницу по её названию
  • post__in — получить записи, идентификатор которых входит в массив
  • post__not_in — получить записи, идентификатор которых не входит в массив

Например, получить черновики страниц и записей:

$query = new WP_Query( array(
    'post_type' => array( 'post', 'page' ),
    'post_status' => 'draft',
) );

Получить страницу по адресу /about/contacts:

$query = new WP_Query( array(
    'pagename' => 'about/contacts',
) );

Получить все запланированные записи и записи на утверждении, кроме тех, которые указаны в массиве post__not_in:

$query = new WP_Query( array(
    'post_type' => 'post',
    'post_status' => array( 'future', 'pending' ),
    'post__not_in' => array( 40, 41, 42 ),
) );

При работе с иерархическими типами записей вы так же можете воспользоваться параметрами post_parent, post_parent__in и post_parent__not_in для того, чтобы задать поиск с ограничением по родительским записями.

Авторы

Для поиска записей по авторам существуют следующие параметры:

  • author — идентификатор автора
  • author_name — ярлык автора
  • author__in — записи авторов, указанных в массиве
  • author__not_in — все записи, кроме авторов, указанных в массиве

Например, получить записи авторов, указанных в массиве:

$query = new WP_Query( array(
    'author__in' => array( 12, 13, 14 ),
) );

Получить записи текущего пользователя:

$query = new WP_Query( array(
    'author' => get_current_user_id(),
) );

Поиск по ключевым словам и датам

Для поиска по ключевым словам и ограничения результатов по датам в WP_Query используются следующие параметры:

  • s — ключевое слово или фраза
  • year — год, например 2013
  • monthnum — порядковый номер месяца, от 1 до 12
  • w — номер недели, от 0 до 53
  • day — день, от 1 до 31
  • day — день, от 1 до 31
  • hour — час, от 0 до 23
  • minute — минута, от 0 до 60
  • second — секунда, от 0 до 60
  • m — год и месяц слитно, например 201311 (ноябрь, 2013)

Начиная с WordPress версии 3.7, в WP_Query доступен новый параметр date_query, который позволяет выполнять запросы со сложными датами. Про работу с date_query и с классом WP_Date_Query читайте в нашей статье.

Пример поиска записей, содержащих ключевую фразу и опубликованных в 2013 году:

$query = new WP_Query( array(
    's' => 'ключевая фраза',
    'year' => 2013,
) );

Порядок и пагинация

Для установления порядка вывода результатов в WP_Query используются следующие параметры:

  • orderDESC для сортировки по убыванию (по умолчанию) или ASC для сортировки по возрастанию
  • orderby — поля, по которым производить сортировку, например (список не полный):
    • date — по дате публикации (по умолчанию)
    • ID — по уникальному идентификатору записи
    • title — по заголовку записи
    • rand — в случайном порядке
    • comment_count — по количеству комментариев
    • post__in — в соответствии с заданным массивом

Для пагинации в WP_Query используются всего два основных параметра:

  • posts_per_page — количество записей на каждую страницу
  • paged — номер страницы

Например, получить три записи, отсортированные по заголовку в порядке возрастания:

$query = new WP_Query( array(
    'order' => 'ASC',
    'orderby' => 'title',
    'posts_per_page' => 3,
) );

Этот же запрос со следующими тремя записями:

$query = new WP_Query( array(
    'order' => 'ASC',
    'orderby' => 'title',
    'posts_per_page' => 3,
    'paged' => 2,
) );

Кстати, чтобы получить все записи, можно указать -1 в качестве параметра posts_per_page. Делать это не желательно, поскольку записей может оказаться больше чем вы ожидаете, и серверу может не хватить памяти для их обработки и хранения.

Так же следует отметить, что прилепленные записи (sticky posts) добавляются автоматически в начало возвращаемых результатов WP_Query. Если вы хотите получить результаты без учёта прилепленных записей, воспользуйтесь параметром ignore_sticky_posts.

Работа с мета-данными и таксономией

Для запроса записей по мета-данным (произвольным полям) или терминам, в WP_Query существуют два основных параметра: meta_query и tax_query. О том, как ими пользоваться мы расскажем в отдельной статье.

Использование WP_Query в темах и плагинах

Важно понимать, что при создании нового объекта класса WP_Query мы делаем очередной запрос в базу данных, и при этом он никак не влияет на наш основной запрос, который WordPress выполняет при загрузке любой страницы. Это касается как WP_Query, так и функций get_posts() и query_posts(), которые всего лишь оборачивают обращение к WP_Query.

Рассмотрим простую ситуацию:

// Основной цикл
while ( have_posts() ) :
    the_post();
    the_title(); // вывести заголовок

    // Вторичный цикл, например схожие статьи
    $related = new WP_Query( ... );
    while ( $related->have_posts() ) :
        $related->the_post();
        the_title(); // заголовок схожей статьи
    endwhile;
endwhile;

В первом обращении функция the_title() выведет заголовок статьи из нашего основного цикла, а второе обращение — из вторичного цикла, хотя функция одна и та же. За это отвечает вызов метода the_post(), который не только делает «шаг в цикле», но и готовит глобальную переменную $post для работы с функциями the_title(), the_content() и прочими.

Именно поэтому, если слегка изменить наш код:

// Основной цикл
while ( have_posts() ) :
    the_post();
    the_title(); // вывести заголовок

    // Вторичный цикл, например схожие статьи
    $related = new WP_Query( ... );
    while ( $related->have_posts() ) :
        $related->the_post();
        the_title(); // заголовок схожей статьи
    endwhile;

    // Снова заголовок. в чём ошибка?
    the_title();
endwhile;

Мы получаем, на первый взгляд, совершенно непредсказуемый результат. Третий вызов функции the_title() выведет заголовок последней записи из вторичного цикла, несмотря на то, что вызов функции находится за пределами этого цикла.

Дело в том, что вызов the_post() изменил нашу глобальную переменную $post для работы с данными вторичного цикла, а после завершения цикла мы так и не вернули данные на свои места. В нашем случае это вполне очевидно, но что если после блока со схожими записями у нас располагается блок комментариев, или блок «поделиться»?

Функция wp_reset_postdata()

Функция wp_reset_postdata() устанавливает глобальную переменную $post в её исходное значение: текущая запись основного цикла. Использовать данную функцию следует сразу же после завершения нашего вторичного цикла:

// Основной цикл
while ( have_posts() ) :
    the_post();
    the_title(); // вывести заголовок

    // Вторичный цикл, например схожие статьи
    $related = new WP_Query( ... );
    while ( $related->have_posts() ) :
        $related->the_post();
        the_title(); // заголовок схожей статьи
    endwhile;

    // Вернуть $post в исходное значение
    wp_reset_postdata();

    // Всё ок!
    the_title();
endwhile;

Поэтому при использовании вторичных циклов в ваших темах и плагинах не забывайте возвращать глобальную переменную $post в её исходное значение. А если вы изменили глобальную переменную $wp_query (что делать не рекомендуется), например с помощью функции query_posts(), её можно вернуть в исходное значение с помощью функции wp_reset_query().

Пример простого плагина

Для полноценного примера, предлагаем рассмотреть простой плагин, который добавляет блок с пятью последними записями к содержимому каждой статьи:

<?php
/**
 * Plugin Name: Append Latest Posts to Content
 */
function my_append_latest_posts( $content ) {
    $query = new WP_Query( array(
        'posts_per_page' => 5,
        'post__not_in' => array( get_the_ID() ),
    ) );

    if ( ! $query->have_posts() )
        return $content;

    $content .= '<h3>Свежие записи</h3>';
    $content .= '<ul>';

    while ( $query->have_posts() ) :
        $query->the_post();
        $content .= sprintf( '<li><a href="%s">%s</a></li>',
            esc_url( get_permalink() ),
            get_the_title()
        );
    endwhile;

    $content .= '</ul>';

    // Не забываем
    wp_reset_postdata();

    return $content;
}
add_action( 'the_content', 'my_append_latest_posts' );

При активации данного плагина пять последних статей будут выводиться списком со ссылками после содержимого каждой записи. Обратите внимание на параметр post__not_in, с помощью которого мы исключаем текущую запись из нашего списка.

Последние записи с помощью WP_Query

Последние записи

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


После того, как вы освоили основы работы с классом WP_Query, мы советуем ознакомиться с его остальными методами и переменными, например get_query_var(), get_queried_object(), $found_posts, $query_vars и другие. Если вам интересно узнать, как именно работает WP_Query, загляните в файл wp-includes/query.php.

Если у вас возникли вопросы при работе с WP_Query, оставьте комментарий и мы обязательно вам ответим.

Подписаться на рассылку

Подписаться → Подпишитесь на бесплатную рассылку журнала WP Magazine и получайте новости, события, подборки тем и плагинов, уроки, советы и многое другое в мире WordPress!

  • Спасибо, действительно полезный сниппет!

  • looler

    Отлично, думаю если будет много подобных статьей, цены Вам не будет )))) хотя Вы и так хороши )
    Спасибо за отличную и МЕГА полезную статью!!!!

  • дельный пост

  • Владимир Шевченко

    Это все гуд и известно)
    такой вопрос: у меня есть произвольное поле типа чекбокс, выводить пост на такой-то странице (таких постов пара десятков)

    вот в настройках у меня стоит выводить по 4 поста в блоке определенной страницы, далее я их аяксом добавляю типа loadmore

    но когда я добавляю проверку на включенность чекбоксов, то в блоке вместо 4 постов я получаю 1 или 2, лоад следующих 4, дает еще 1-2 поста

    как быть с такой проверкой?

    • Владимир, для такой задачи вам нужно работать с параметром meta_query, где вы можете указывать ключ вашего произвольного поля и требуемое значение, тогда WP_Query вернёт вам только те записи, которые вам требуются. Про meta_query и tax_query мы планируем отдельные статьи :)

    • chumachkin_m

      WP_Query создает SQL запрос, сам запрос можно посмотреть в свойстве request объекта.

  • Имхо, WP_Query — это практически альфа и омега WordPress’а. Только этого минимума уже достаточно для создания сайтов средней сложности.

    Для того, кто хочет использовать движок WP наиболее полноценно и правильно, стоит заучить раздел кодекса о WP_Query от корки до корки, однозначно.

  • chumachkin_m

    Я считаю, что WP_Query это изобретение велосипеда — при чем не очень удачное и конечно это исторически сложилось.
    Почему бы не использовать уже готовый ORM, к примеру Doctrine?

    • Спасибо за ваше мнение, кстати никто вам не мешает использовать Doctrine или любой другой ORM вместе с WordPress, главное при этом знать что происходит внутри WP_Query ;)

      • chumachkin_m

        Я может с Doctrine палку перегнул :) , но столкнулся с тем, что мне надо было скомбинировать группы параметров в meta_query, что я бы легко сделал с помощью SQL. Но как-то не интуитивно получается с использованием массива.

        Я знаю, что есть параметр в meta_query relation, но для того, что бы понять применяется он ко всем meta_query или можно его применить к группе, надо либо изучать код WP_Query, либо изучать результирующий SQL в свойстве request.

        К тому же, уже образуется двойное вложение массива в массив и напрашивается еще одно вложение. По моему, само собой напрашивается создание дополнительных методов для создания более сложных SQL структур.

        ЗЫ.
        Но это только мое мнение.

        • WP_Query не может полностью заменить SQL. Всегда найдется то, что можно сделать с помощью SQL и нельзя сделать с помощью WP_Query, и именно для этого существует ряд фильтров, например posts_where, posts_orderby и т.д., а если уж совсем не получается, то вам всегда поможет $wpdb :)

          • chumachkin_m

            Спасибо, за подсказку с фильтрами, так как столкнулся с pre_get_posts, в результате пришлось поиметь очень много исключений в условие.

            if ( $query->is_main_query() && $query->query_vars[‘category_name’] != $news_category->slug && $query->query_vars[‘category_name’] != $article_category->slug && !is_single() && !is_page() ) {
            $query->set(‘meta_query’,
            array (
            ‘relation’ => ‘AND’,
            array(
            ‘key’ => ‘coupon_date_end’,
            ‘type’ => ‘datetime’,
            ‘value’ => date(«Y-m-d H:i:s»),
            ‘compare’ => ‘>=’
            ),
            )
            );
            }

  • Sergey Shinove

    Шикарная статья!! спасибо большое)

  • Дмитрий

    wp_reset_query(); вместо wp_reset_postdata(); будет ошибкой?

  • Eugene

    Отличная статья. Все по полкам, спасибо!

  • Виктор

    Привет Константин,
    помоги пожалуйста доделать код:
    have_posts()) : $my_query->the_post();

    the_content( __( 'Continue reading →', 'twentytwelve' ) );

    endwhile; endif;
    Этот код выводит текст на главной из указанной страницы до тега more. Проблема в том что главных страниц много page/2, page/3 и т.д. на них текст уже пропадает. Как сделать код что на всех главных страница был текст из указанной страницы?

    • Покажите код целиком, на pastebin.com или Gist.

  • Сергей

    Большое спасибо!

  • Уберите $paged из вашего условия.

  • Amid

    Подскажите где может лежать текст «Latest posts» который генерируется для названия «последние записи» Latest Posts
    Latest Posts ….. это из хрома подсвечивает так

  • Приветствую, спасибо за развернутый мануал.
    С тем, как и что вывести понятно. Стоит задача выводить 10 записей произвольного типа в рандомном порядке за день.

    То есть, сегодня загрузились 10 записей (random), завтра загрузятся 10(random) других и т.д. Исключать повторы не обязательно.
    Как такое организовать?

    Благодарю!

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

      • dimasmagadan

        я делаю псевдо-рандомные выборки так
        https://gist.github.com/Dimasmagadan/102bcfad9a83a2d641eb

        в двух словах — получаем тяжелым запросом бОльшее, чем нужно, количество постов, кэшируем результат, перед выводом перемешиваем массив php функцией, выводим первые n-записей.

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

        То есть — по такой же схеме получаем результат запроса с избыточными данными в виде json, но дальше сохраняем в локал сторидж, перемешиваем его на js, показываем часть на странице. На другой странице — проверяем наличие запроса в локал сторидж, если есть, перемешиваем и показываем.

        • Отличная идея. Именно это я и имел ввиду словами «делать лишь вид того, что записи выбираются случайным образом».

          Но думаю AJAX запросы это не самый лучший вариант с точки зрения производительности, т.к. вы подобным образом будете получать закэшированную страницу, но незакэшированные данные из AJAX, т.е. один «некэшируемый» запрос у вас на сервер все же будет проскакивать, а это запуск окружения WordPress, загрузка всех плагинов и т.д., т.е. может быть дорого, особенно на крупных сайтах.

          Вместо этого можно странице сразу передать массив из десяти записей, и выбирать случайную уже средствами JavaScript. Таким образом ответ целиком будет выдаваться из кэша страниц, а блок будет все равно казаться рандомным.

          • dimasmagadan

            не думаю, что автор вопроса строит какую-то высоконагруженную систему. иначе, вопрос по ускорению рандомной выборки он бы не задал)

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

            с вашим подходом при добавлении комментария нужно будет перестроить кэш всех таких страниц, тк массив с данными записей будет в теле страницы
            с моим при посещении сайта будет 1 дополнительный ajax запрос (возможно данные будут с transient cache), результаты которого на какое-то время сохраняться в local storage и на остальных страницах будут браться с этого хранилища.

            но, какой вариант использовать, лучше подбирать в зависимости от проекта.

  • Sergey Arustamov

    Большое вам спасибо за этот гист!

  • В плагине не получается вытащит произвольные поля через

    $args = array(
    ‘post_type’ => ‘product’
    );

    $homepage_query = new WP_Query($args);

    Пробовал еще так $a = get_metadata(‘product’, (int)$post_id, ‘_regular_price’, true);

    $post_id — проверил айдишник плагин получает.

    Возможно где-то надо взять глобальную переменную или объект?

    Вообще основная задача в моем плагине достать цену из woocommerce.

  • Ivan Panfilov

    поможет ли WP_Query
    если нужно сделать типа так

    category__in (1,2) — это два значения характеристики1
    AND
    category__in (5,6) — это два значения характеристики2
    AND
    category__in (10) — это значение характеристики3

    и как это сделать.

    смысл думаю понятен — типовой фильтр по характеристикам товара например

    или же придется свой запрос составлять?

  • Евгений

    А вот подскажите как во вторичном цикле исключить первую запись???

  • Алексей

    Кто может помочь?
    Не знаю как внедрить в цикл WP_q… пагинацию, специально для вывода статей из конкретной категории. В PHP очень слаб.

  • Спасибо за статью. такой вопрос который хотелось бы у вас уточнить.
    Как мне вывести одни статьи, одного типа поста и при этом одной таксономии, но при этом исключить совпадение ссылок. т.е. я хочу вывести похожие статьи с помощью WP_Query

    сейчас я вывожу таким образом.

    $args=array(
    ‘post_type’ => ‘pressa’,
    ‘showposts’=>5, //Количество выводимых записей
    /* ‘pressa_tax’ => ‘2010-2012’, это работает, но надо сделать, чтобы в зависимости от id pressa_tax выводились похожие записи */
    /* ‘caller_get_pressa’=>1); // Запрещаем повторение ссылок — этот блок не работает.*/

    $my_query = new wp_query($args);

    Я понимаю, что надо на этап раньше провести выборку из pressa_tax, но не знаю как это сделать.
    Подскажите пожалуйста.

    И как запретить повторение ссылок? Чтобы на странице не было статей с ссылкой на саму себя

    • Чтобы исключить ту или иную запись из WP_Query, воспользуйтесь аргументом post__not_in, а что касается вашего первого вопроса, он не совсем понятен. Попробуйте его сформулировать по-другому.

      • Я хочу вывести похожие записи (статьи) для одиночной записи post_type = pressa, которая имеет таксономию pressa_tax, при этому в пресса_такс несколько категорий, и я хочу если запись в категории 2010 — 2012 ей показывались только похожие статьи этой категории.
        Скажите а post__not_in может исключать, как-то статьи с текущим ID? Т.е. чтобы в похожих записях не было повторяющихся постов?

        • Да, смотрите в статье есть пример с post__not_in и указанием нескольких ID, в вашем случае будет один ID текущей записи.