ГлавнаяРазноеРабота с произвольными полями в WP_Query

Работа с произвольными полями в WP_Query

Произвольные поля (или мета-данные) в WordPress позволяют добавлять дополнительную информацию к записям, страницам и пользовательским типам данных. Использовать произвольные поля можно в самых разных сценариях, например:

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

Работа с произвольными полями в WordPress

Помимо сохранения и отображения произвольных полей, часто приходится искать, фильтровать и сортировать по этим полям. В этом нам помогут классы WP_Query и WP_Meta_Query.

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

Параметр meta_query

Основным параметром для работы с мета-данными в WP_Query является meta_query, который реализован классом WP_Meta_Query. Параметр meta_query — это массив, где каждый элемент является в свою очередь массивом с параметрами мета-запроса. Рассмотрим простой пример:

$query = new WP_Query( array(
    'post_type' => 'post',
    'meta_query' => array(
        array(
            'key' => 'mood',
            'value' => 'happy',
        ),
    ),
) );

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

Как видно из примера, meta_query содержит массив с одним элементом, который указывает ключ и значение произвольного поля с помощью key и value. Данный пример вернет записи, которые мы опубликовали в хорошем настроении.

Каждый массив в meta_query может содержать следующие элементы:

  • key — это ключ (или имя) нашего произвольного поля
  • value — значение произвольного поля, строкой или массивом
  • compare — тип сравнения, например: =, !=, >, >=, <, <=, LIKE, NOT LIKE, IN, NOT IN, BETWEEN, NOT BETWEEN, EXISTS, NOT EXISTS
  • type — тип значения, например: NUMERIC, BINARY, CHAR, DATE, DATETIME, DECIMAL, SIGNED, TIME, UNSIGNED

Рассмотрим еще один пример с использованием чисел и другого типа сравнения:

$query = new WP_Query( array(
    'post_type' => 'property',
    'meta_query' => array(
        array(
            'key' => 'price',
            'value' => 50000,
            'compare' => '<=',
            'type' => 'NUMERIC',
        ),
    ),
) );

Здесь мы запрашиваем у WP_Query записи произвольного типа property, где значение произвольного поля price меньше или равно 50,000.

Важно отметить, что все значения произвольных полей в WordPress имеют текстовый тип, а значение параметра type позволяет преобразовать все значения в требуемый тип перед сравнением. В нашем случае это число (NUMERIC).

Несколько запросов meta_query

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

$query = new WP_Query( array(
    'post_type' => 'product',
    'meta_query' => array(
        array(
            'key' => 'color',
            'value' => 'red',
        ),
        array(
            'key' => 'size',
            'value' => 'XL',
        ),
    ),
) );

Данный запрос вернет записи типа product у которых в произвольных полях указан красный цвет и размер XL. По умолчанию в meta_query запросы будут производиться с оператором "и", а не "или", но это легко изменить с помощью аргумента relation:

$query = new WP_Query( array(
    'post_type' => 'product',
    'relation' => 'OR',
    'meta_query' => array(
        array(
            'key' => 'color',
            'value' => 'red',
        ),
        array(
            'key' => 'color',
            'value' => 'blue',
        ),
    ),
) );

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

$query = new WP_Query( array(
    'post_type' => 'product',
    'meta_query' => array(
        array(
            'key' => 'color',
            'value' => array( 'red', 'blue' ),
            'compare' => 'IN',
        ),
    ),
) );

Сортировка с помощью произвольного поля

Часто при работе с мета-данными в WordPress приходится учитывать их при сортировке результатов. За это в классе WP_Query отвечает параметр orderby, где можно указать:

  • meta_value - сортировать по значению мета-поля
  • meta_value_num - сортировать по численному значению мета-поля
  • для сортировки по дате, заголовку и другим полям см. нашу статью

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

$query = new WP_Query( array(
    'post_type' => 'auto',
    'meta_key' => 'price',
    'orderby' => 'meta_value_num',
    'order' => 'ASC',
) );

Более сложный пример: вывод красных автомобилей мощностью от 100 до 150 лошадиных сил, и сортировка по цене по убыванию:

$query = new WP_Query( array(
    'post_type' => 'auto',
    'meta_query' => array(
        array(
            'key' => 'color',
            'value' => 'red',
        ),
        array(
            'key' => 'hp',
            'value' => array( 100, 150 ),
            'compare' => 'BETWEEN',
            'type' => 'NUMERIC',
        ),
    ),
    'meta_key' => 'price',
    'orderby' => 'meta_value_num',
    'order' => 'DESC',
) );

Производительность meta_query

Параметр meta_query действительно позволяет делать многое с произвольными полями в WordPress, но не стоит забывать о производительности, особенно если речь идет о большом количестве данных и о сложных запросах с мета-полями.

Поле meta_value таблицы wp_postmeta в базе данных MySQL не имеет индекса из-за его типа LONGTEXT. Но даже если бы был индекс, пользы от него было бы мало, поскольку запросы с meta_query используют MySQL функцию CAST для перевода типа данных при поиске, а присутствие подобной функции в запросе значит, что индекс использован не будет.

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

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

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

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

  • Anatoly Yumashev

    Спасибо за статью! Давно размышлял над этим механизмом и пока не могу однозначно ответить на такой вопрос…

    Скажем если я хочу работать только с базовыми возможностями архива WP, то я могу например отфильтровать записи по 5 терминам 2-х таксономий. Просто указав их в URL.

    И я могу это сделать если речь идет о одном метаполе и одном значении.

    А если мне нужно отобрать записи по 2м или 3м полям и по 2м или 3м значениям? Например: Дом 3 комнаты в пределах 1 млн руб. При условии что число комнат и цена — это метаполя.
    Пример взял абстрактный.

    В этом случае стандартных возможностей URL мне не хватит и нужно будет писать свои механизмы обработки по своим параметрам? Верно понимаю?

    • Анатолий, для таксономии да (при условии, что они зарегистрированы как public), но для мета-данных перевода из URL в WP_Query по умолчанию нет. То есть вы не можете добавить к URL &meta_key=price&meta_value=500, это не сработает.

      Если вы хотите использовать мета-данные в строке запроса, вам потребуется фильтр query_vars, функция get_query_var() и событие pre_get_posts. А если вы хотите еще и красивые пермалинки, скажем /price/500, тогда еще и методы класса WP_Rewrite.

      • zelenin

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

        • versusbassz

          +1

        • Спасибо за комментарий. Статьи по WP_Rewrite обязательно у нас появятся :)

          • Zakhar

            Здравствуйте, стоит ли в ближайшее время ждать пост про использование WP_Rewrite при использовании фильтров по кастомным метаполям

  • Спасибо за полезную информацию! Постоянно использую WP_Query, но таких тонкостей работы с произвольными полями не знал.

    • Спасибо за комментарий!

  • MrClon

    Эт всё конечно очень блаародно, но обращение к произвольному полю это +1 запрос к БД, при-чём без индекса. Реализовал одну фичу на произвольных полях, страницы постов с ней генерятся раза в два дольше чем без неё. Конечно измерения мои неточны, да и тормозить может мой кривой код обрабатывающий данные из полей (хотя там вроде несчего), но запросы мимо индекса всё-же не есть хорошо. Может есть какие-то способы оптимизировать это дело?

    • MrClon, прочитайте последний раздел статьи :)

      • MrClon

        Меня не поиск интересует, да и объёмы не те что-бы прикручивать что-то специфическое. Правильно приготовленного MySQL (например без запростов мимо индексов) было-бы вполне достаточно, но не велосипедить-же свою реализацию кастомных полей, с Го и гейшами.

        • Запрос SELECT это своего рода поиск по базе данных :) Запросов мимо индексов с meta_value к сожалению не получится.

          Несколько лет назад я работал с базой данных именно по недвижимости и приходилось выполнять сложные запросы на мета-данные, что сказалось на скорость при росте базы. Пробовали много разных подходов, одним из самых быстрых оказался триггер на таблице wp_postmeta. При добавлении данных в wp_postmeta это триггер копировал строку с определенными ключами в стороннюю таблицу прогоняя через CAST(), а там поля meta_value_numeric и meta_value_char имели другие типы и соответствующие индексы. Затем с помощью фильтров в WP_Query переписывали часть запросов, для того, чтобы убрать CAST() и использовать стороннюю таблицу. Все работало очень быстро.

          Но поставить Sphinx оказалось гораздо легче :)

          • MrClon

            Тьфу-ты, туплю. Невнимательно прочёл последний абзац, решил что wp_postmeta вообще индексов нет (:
            Мне-то по meta_value запросы строить не нужно, у меня всё цивильно meta_key=’foo’ and post_id=’bar’ (во всяком случае должно быть, не знаю какие запросы WP строит, хотя вроде накосячить тут трудно), так-то всё по индексу должно идти. Значит тормозит мой код, хотя непонятно где.

          • Такой запрос вам вовсе не нужен. Когда вы получаете вашу запись через WP_Query, она считывается целиком из базы данных, включая мета-поля и термины, и записывается в объектный кэш WordPress, откуда можно легко обращаться к этим данным с помощью get_post_meta(). Отключить эту опцию в WP_Query можно с помощью cache_results.

            Плагин Debug Bar поможет вам просмотреть SQL запросы, которые делает WP_Query.

  • Людмила

    Скажите, как можно попасть в свою админку, если доступ закрылся из-за неправильной настрройки SSL?

  • Так вы в строке запроса и задаете сортировку по одному ключу и судя по всему ваш код не исполняется (было бы лучше если бы вы его опубликовали на Pastebin или Gist).

  • Константин, огромное спасибо за столь развернутую статью по WP_Query — очень помогли по нескольким спорным моментам

  • Галина

    Есть проблема, подскажите пожалуйста как решить.
    Запрос:
    $args = array(
    ‘post_type’ => ‘wpsc-product’,
    ‘post_status’ => ‘publish’,
    ‘showposts’ => ‘4’,
    ‘meta_query’ => array(
    ‘relation’ => ‘OR’,
    array(
    ‘key’ => ‘sometime’,
    ‘value’ => 1
    ),
    array(
    ‘key’ => ‘start_date’,
    ‘value’ => $cur_date,
    ‘compare’ => ‘>=’
    )
    ),
    ‘orderby’ => ‘meta_value_num’,
    ‘meta_key’ => ‘start_date’,
    ‘order’ => ‘ASC’
    );

    Т.к. ‘relation’ => ‘OR’ — учитывается поле и ‘meta_key’ => ‘start_date’ — которое я добавляю для сортировки, если его убираю — не сортируются.
    Есть ли способ формировать более сложные запросы с мета данными, чтоб применять relation и «ИЛИ» и «И» для определенных групп полей?

    • Я немножко не по теме отвечу :) Как замечено в статье:

      Поле meta_value таблицы wp_postmeta в базе данных MySQL не имеет индекса из-за его типа LONGTEXT. Но даже если бы был индекс, пользы от него было бы мало, поскольку запросы с meta_query используют MySQL функцию CAST для перевода типа данных при поиске, а присутствие подобной функции в запросе значит, что индекс использован не будет.

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

  • А как например сделать так. Доп.поле цифра. Сделать меню чтобы нажимая на пункт меню 1940 я попадал на перечень страниц с допполем 1940? Можно ответ на мейл drquak@ya.ru

  • Vladimir Poshtaru

    Чуть не по теме, не нашел в поиске пост по моей проблеме. Как присвоить рубрикам в произвольном меню счетчик публикаций? В виджете «рубрики» есть такая настройка, а в произвольном меню похоже нет. Помогите пожалуйста разобраться.