Фильтрация записей по метаполям в WP REST API: практическое руководство

В стандартном WP REST API фильтрация записей по метаполям (post meta) не поддерживается напрямую. Но часто в проектах возникает необходимость получать записи с определёнными значениями метаданных, например, по цене товара, статусу или дополнительным параметрам. В этой статье подробно разберём, как добавить поддержку фильтрации по метаполям в WP REST API, расширяя стандартные эндпоинты и создавая собственные.

Почему фильтрация по метаполям в WP REST API важна

Метаполя — это мощный инструмент для хранения кастомных данных, которые не помещаются в стандартные поля записи. Без возможности фильтрации по ним REST API теряет большую часть гибкости при получении нужной информации. Стандартные параметры запроса позволяют фильтровать только по базовым полям (ID, дата, статус и т.д.), но не по пользовательским метаполям.

Реализация фильтрации по метаполям позволит вам:

  • Получать только релевантные записи с нужными значениями дополнительной информации.
  • Оптимизировать клиентские приложения, уменьшив объём данных и количество запросов.
  • Создавать более сложные и точные запросы для построения интерфейсов.

Расширение стандартного эндпоинта posts для фильтрации по метаполям

Рассмотрим пример добавления параметра meta_key и meta_value в стандартный эндпоинт /wp-json/wp/v2/posts. Для этого используем хук rest_post_query, который позволяет модифицировать WP_Query аргументы.

Добавление фильтрации по одному метаполю

add_filter('rest_post_query', 'wpapi_rest_filter_posts_by_meta', 10, 2);
function wpapi_rest_filter_posts_by_meta($args, $request) {
    $meta_key = $request->get_param('meta_key');
    $meta_value = $request->get_param('meta_value');

    if ($meta_key && $meta_value) {
        $args['meta_query'] = [
            [
                'key' => sanitize_text_field($meta_key),
                'value' => sanitize_text_field($meta_value),
                'compare' => '='
            ]
        ];
    }
    return $args;
}

Теперь, сделав запрос /wp-json/wp/v2/posts?meta_key=price&meta_value=100, мы получим все записи, у которых метаполе price равно 100.

Фильтрация по нескольким метаполям

Чтобы расширить функционал, добавим параметр meta_query в формате JSON, позволяющий задавать несколько условий. Это требует парсинга и валидации.

add_filter('rest_post_query', 'wpapi_rest_filter_posts_by_meta_multiple', 10, 2);
function wpapi_rest_filter_posts_by_meta_multiple($args, $request) {
    $meta_query_json = $request->get_param('meta_query');
    if ($meta_query_json) {
        $meta_query = json_decode($meta_query_json, true);
        if (json_last_error() === JSON_ERROR_NONE && is_array($meta_query)) {
            $safe_meta_query = [];
            foreach ($meta_query as $query) {
                if (isset($query['key'], $query['value'])) {
                    $safe_meta_query[] = [
                        'key' => sanitize_text_field($query['key']),
                        'value' => sanitize_text_field($query['value']),
                        'compare' => isset($query['compare']) ? sanitize_text_field($query['compare']) : '='
                    ];
                }
            }
            if ($safe_meta_query) {
                $args['meta_query'] = $safe_meta_query;
            }
        }
    }
    return $args;
}

Пример запроса:

/wp-json/wp/v2/posts?meta_query=[{"key":"price","value":"100","compare":">="},{"key":"color","value":"red"}]

Этот запрос вернёт записи, где цена больше либо равна 100, и цвет — красный.

Создание отдельного кастомного эндпоинта с расширенной фильтрацией

Если нужна более гибкая логика, лучше создать собственный REST API маршрут. Это позволит полностью контролировать параметры, сортировку и формат ответа.

Регистрация кастомного эндпоинта

add_action('rest_api_init', function () {
    register_rest_route('wpapi/v1', '/posts-filter/', [
        'methods' => 'GET',
        'callback' => 'wpapi_get_filtered_posts',
        'permission_callback' => '__return_true',
        'args' => [
            'meta_query' => [
                'required' => false,
                'validate_callback' => function ($param, $request, $key) {
                    return is_string($param);
                }
            ],
            'per_page' => [
                'required' => false,
                'default' => 10,
                'sanitize_callback' => 'absint'
            ],
            'page' => [
                'required' => false,
                'default' => 1,
                'sanitize_callback' => 'absint'
            ]
        ]
    ]);
});

function wpapi_get_filtered_posts(WP_REST_Request $request) {
    $meta_query_json = $request->get_param('meta_query');
    $per_page = $request->get_param('per_page');
    $page = $request->get_param('page');

    $args = [
        'post_type' => 'post',
        'posts_per_page' => $per_page,
        'paged' => $page,
    ];

    if ($meta_query_json) {
        $meta_query = json_decode($meta_query_json, true);
        if (json_last_error() === JSON_ERROR_NONE && is_array($meta_query)) {
            $args['meta_query'] = [];
            foreach ($meta_query as $query) {
                if (isset($query['key'], $query['value'])) {
                    $args['meta_query'][] = [
                        'key' => sanitize_text_field($query['key']),
                        'value' => sanitize_text_field($query['value']),
                        'compare' => isset($query['compare']) ? sanitize_text_field($query['compare']) : '='
                    ];
                }
            }
        }
    }

    $query = new WP_Query($args);

    $posts_data = [];
    foreach ($query->posts as $post) {
        $posts_data[] = [
            'id' => $post->ID,
            'title' => get_the_title($post),
            'link' => get_permalink($post)
        ];
    }

    return [
        'posts' => $posts_data,
        'total' => (int) $query->found_posts,
        'pages' => (int) $query->max_num_pages
    ];
}

Пояснения к реализации

Мы создаём новый маршрут /wp-json/wpapi/v1/posts-filter/, который принимает параметр meta_query в формате JSON, а также пагинацию. В ответ возвращаем упрощённый список записей с ID, заголовком и ссылкой, а также данные о количестве страниц.

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

/wp-json/wpapi/v1/posts-filter/?meta_query=[{"key":"color","value":"blue"},{"key":"size","value":"large"}]&per_page=5&page=2

Использование плагинов для расширения фильтрации REST API

Если хотите готовое решение с минимальным кодом, обратите внимание на плагины, которые расширяют WP REST API:

  • Clearfy Pro — содержит расширенные настройки безопасности и оптимизации REST API, включая фильтрацию и ограничение доступа.
  • WPRemark — плагин для расширения функционала REST API с поддержкой кастомных полей и фильтров.

Использование плагинов позволяет быстрее внедрить фильтрацию, но собственный код даёт максимальную гибкость и контроль.

Советы по безопасности и производительности при фильтрации по метаполям

При фильтрации по метаполям важно учитывать несколько моментов:

  • Всегда валидируйте и санитизируйте входящие данные, чтобы избежать SQL-инъекций и XSS.
  • Метаполя не индексируются по умолчанию, поэтому сложные запросы по ним могут быть медленными. Для ускорения стоит создать индексы в базе данных или использовать кэширование ответов.
  • Ограничивайте количество возвращаемых записей через параметр per_page и реализуйте пагинацию.
  • При большом объёме данных рассмотрите использование внешних решений для поиска и фильтрации, например, Elasticsearch с интеграцией через REST API.

Шаблоны для WP Плагины для WP