WP REST API — параметры для фильтрации запросов.

Это продолжение статьи “WP REST API(дефолтное) — взаимодействие с WP сайтом,” в которой я повествовал о работе с дефолтным апи вордпрес. Однако, той статье я ничего не рассказываю о пагинации или получении связанных обьектов в одном запросе, ну и тд… в общем, эта статья ксть дополнением к той(по этому прочтите ее).

Параметры пагинации

Предположим, у нас появилась потребность получить обьекты постов запросом к WP REST API. В случаи если ЧПУ на сайте включены, запрос будет выглядеть так

https://guten.website/wp-json/wp/v2/posts

Cейчас мы получим JSON-строку, содержащую массив обьектов всех постов(или 100, если их больше). Но если нам не нужны они все? Здесь все просто – нужно указать параметры пагинации(аналогичные им же в обьекте WP_Query). Этот запрос ограничит нашу выборку 5-ю постами ч 2-ой страницы

https://guten.website/wp-json/wp/v2/posts?per_page=5&page=2

Как видно, были добавлены 2 GET-параметра – per_page и page. На деле их 4(5)(если порядок сортировки результатов относить к пагинации).

Третий параметр(offset) позволяет настроить сдвиг, т.е. количество постов, которое нужно пропустить(еще не придумал ситуации где он нужен, может, для разбития самой страницы…). И четвертый(order и orderby, да, их два – обманул) – определяют поле для сортировки(второй) и порядок сортировки(первый).

В итоге, запрос, который будет выводить последние 3 поста с второй страницы, содержащей по 5 постов на страницу и в порядке убывание id, выглядит так

https://guten.website/wp-json/wp/v2/posts?per_page=3&page=2&offset=2&sortby=id&sort=desc

Сортируемые поля у каждого типа обьекта разные… и, к полной картине, заголовки ответа(в полях X-WP-Total и X-WP-TotalPages) содержится

Заголовки HTTP-ответа
Заголовки HTTP-ответа

информация о количестве записей и количестве страниц, при данном разбиении. Немного уточнений по offset – он отбрасывает не n первых постов на каждой странице, а n первых постов согласно параметров сортировки.

Ссылание и встраивание

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

Для примера, воспользуемся обращением к постам; ответ содержит только id изображения миниатюры, но не саму ссылку на него(которую можно вставить в тег img, к примеру). В поле _links есть следующий обьект

Ссылка на связанный обьект
Ссылка на связанный обьект

В этом участке информации меня интересует не так ссылка на обьект, как то, что поле embeddable имеет значение true, т.е. обьект может быть встроен в текущий. Чтобы его встроить, нужно к текущему запросу дописатьь еще один гет-параметр _embed с значением нужного обьекта; в моем случаи

https://guten.website/wp-json/wp/v2/posts?per_page=5&page=2&_embed=wp:featuredmedia

при этом в каждом родительском объекте появится поле _embedded, содержащий требуемый обьект(равный тоому, который будет ответом по ссылке).

Встроенный объект
Встроенный объект

Фильтр полей

В WP REST API есть get-атрибут _fields, позволяющий заказать поля, которые нужно получить. Это нужно, как вы понимаете, для оптимизации запросов к серверу, что б он не “лег” в пиковые моменты… но не только! Также, этим параметром можно заказывать различные зарегистрированные метаполя. Рассмотрим пример получения id, title и метаполя counter; для начала зарегистрируем его в rest

<?php
// как мета данные
register_meta( 'post', 'counter', array(
	'type'              => 'string',
	'description'       => "Счетчик просмотров",
	'single'            => true,
	'sanitize_callback' => null,
	'auth_callback'     => null,
	'show_in_rest'      => true,
) );
// как обычное поле
add_action( 'rest_api_init', function(){

	register_rest_field( 'post', 'counter', array(
		'get_callback' => function( $post ){
			return get_post_meta( $post->ID, 'counter', true );
		},
		'update_callback' => null,
		'schema' => [
			'description' => "Счетчик просмотров", 
			'type' => 'string'
		],
	) );

} );

теперь можно получить коллекцию обьектов

https://guten.website/wp-json/wp/v2/posts?_fields=id,title,meta.counter
# или
https://guten.website/wp-json/wp/v2/posts?_fields=id,title,counter

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

WP REST API(кастомное) — взаимодействие с WP сайтом.

В прошлой статье мы обсудили стандартное WP REST API, принцип чтения о нем информации и использования. В этой части рассмотрим создание и расширение существующего программного интерфейса.

Содержание

Вступление. WP REST API к кастомному типу записи

Предположим, есть задача создать тип записи. Рассмотрим код выполнения этой задачи, приведенный в официальной документации

<?php
add_action( 'init', 'codex_book_init' );
/**
 * Register a book post type.
 *
 * @link http://codex.wordpress.org/Function_Reference/register_post_type
 */
function codex_book_init() {
	$labels = array(
		'name'               => _x( 'Books', 'post type general name', 'your-plugin-textdomain' ),
		'singular_name'      => _x( 'Book', 'post type singular name', 'your-plugin-textdomain' ),
		'menu_name'          => _x( 'Books', 'admin menu', 'your-plugin-textdomain' ),
		'name_admin_bar'     => _x( 'Book', 'add new on admin bar', 'your-plugin-textdomain' ),
		'add_new'            => _x( 'Add New', 'book', 'your-plugin-textdomain' ),
		'add_new_item'       => __( 'Add New Book', 'your-plugin-textdomain' ),
		'new_item'           => __( 'New Book', 'your-plugin-textdomain' ),
		'edit_item'          => __( 'Edit Book', 'your-plugin-textdomain' ),
		'view_item'          => __( 'View Book', 'your-plugin-textdomain' ),
		'all_items'          => __( 'All Books', 'your-plugin-textdomain' ),
		'search_items'       => __( 'Search Books', 'your-plugin-textdomain' ),
		'parent_item_colon'  => __( 'Parent Books:', 'your-plugin-textdomain' ),
		'not_found'          => __( 'No books found.', 'your-plugin-textdomain' ),
		'not_found_in_trash' => __( 'No books found in Trash.', 'your-plugin-textdomain' )
	);

	$args = array(
		'labels'             => $labels,
		'description'        => __( 'Description.', 'your-plugin-textdomain' ),
		'public'             => true,
		'publicly_queryable' => true,
		'show_ui'            => true,
		'show_in_menu'       => true,
		'query_var'          => true,
		'rewrite'            => array( 'slug' => 'book' ),
		'capability_type'    => 'post',
		'has_archive'        => true,
		'hierarchical'       => false,
		'menu_position'      => null,
		'supports'           => array( 'title', 'editor', 'author', 'thumbnail', 'excerpt', 'comments' )
	);

	register_post_type( 'book', $args );
}

Результатом выполнения этого кода будет пункт «Books» и новый тип записи «book» на сайте. Но данные о записях данного типа будут не доступные по АПИ, для этого нужно изменить массив аргументов

<?php
	$args = array(
		'labels'             => $labels,
		'description'        => __( 'Description.', 'your-plugin-textdomain' ),
		'public'             => true,
		'publicly_queryable' => true,
		'show_ui'            => true,
		'show_in_menu'       => true,
		'query_var'          => true,
		'rewrite'            => array( 'slug' => 'book' ),
		'capability_type'    => 'post',
		'has_archive'        => true,
		'hierarchical'       => false,
		'menu_position'      => null,
		'supports'           => array( 'title', 'editor', 'author', 'thumbnail', 'excerpt', 'comments' ),
        'show_in_rest'       => true,// включает отображение апи
        'rest_base'          => 'book',// путь для апи, по-умолчанию, равно типу записи
        'rest_controller_class' => 'WP_REST_Posts_Controller'// класс, строящий апи, по-умолчанию "WP_REST_Posts_Controller"
	);

Данный код создаст стандартный «вордпрессовский » API. Кстати, плюсом к включению АПИ в типе поста будет также включатся редактор Guttenberg в админке.

При этом автоматически будет сгенерированная схема.

Создадим WP REST API к какому-нибудь контенту

На самом деле, этот путь можно использовать и для АПИ типов постов, но зачем?.. ведь разработчики предусмотрели эту возможность, и я за то, чтоб максимально использовать все, что усмотрено разрабами.

Но тем не менее, рассмотрим этот способ. Создадим эндпоинт-функцию и прикрепим к ней маршрут

<?php
function custom_rest_route( $get ) {
    return $get['message'];
}
function rest_callback() {
    register_rest_route( 'custom/v1', '/custom/(?P<message>w+)', array(
        'methods' => 'GET',
        'callback' => 'custom_rest_route'
    ));
}
add_action( 'rest_api_init', 'rest_callback');

Здесь «(?Pw+)» — обычное регулярное выражение. В данном случаи, перейдя по пути http://example.com/wp-json/custom/v1/custom/hello увидим hello на экране. Этот маршрут, также, появиться в схеме.

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

Расширяем WP_REST_Controller

Прежде, чем расширять класс, нужно знать его методы(это касается любого расширяемого класса). Класс WP_REST_Controller имеет более 25 методов, однако, для создания на его базе своего интерфейса достаточно знать 5:

  • register_routes — регистрация маршрутов(их может быть много);
  • get_items, get_item, create_item, update_item, delete_item — методы, отвечающие на запросы.

Я не буду ничего усложнять и просто перепишу созданное ранее в виде класса

<?php
function rest_callback() {
    $route = new Custom_REST_Controller();
    return $route->register_routes();
}
add_action( 'rest_api_init', 'rest_callback');

class Custom_REST_Controller extends WP_REST_Controller {
    function register_routes() {
        register_rest_route( 'custom/v1', '/custom/(?P<message>d+)', array(
            'methods' => WP_REST_Server::READABLE,
            'callback' => array( $this, 'get_item' )
        ));
    }
    function get_item( $request ) {
        return new WP_REST_Response( $get['message'], 200 );
    }
}

Поскольку мой пример только выводит надпись — я использую только get_item(). Вообще, это минимальный минимум для создания маршрута и эндпоинта.

Редактирование эндпоинтов и схемы. Фильтры

Редактирование результатов работы функции, согласно философии wordpress, происходит по средству фильтров. Данное утверждение касается абсолютно всех функций этого движка.

Работают фильтры так: есть функция

<?php
function some_func( $args ) {
    // do something
    $args = apply_filters( 'slug', $args );// filter install
    // do something
}

Если в функции установлен фильтр, то передаваемые аргументы в этом фильтре можно изменить

<?php
function filter_func( $args ) {
    // do something with args
    return $args;
}
add_filter( 'slug', 'filter_func', 10, 1 );

Здесь 10 — приоритет выполнения функции фильтра, 1 — количество принимаемых аргументов. Приоритет может изменятся если функций-фильтров несколько, чем больше это число — тем позже будет срабатывать фильтр. И еще, регистрация фильтра(add_filter) должна происходить раньше, чем вызов функции, которую нужно отфильтровать.

Но хватит теории, изменим программный интерфейс. Для начала, нужно знать слаг фильтра, его можно найти в документации вордпресс.

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

<?php
return apply_filters( 'rest_pre_insert_user', $prepared_user, $request );

значит, чтоб вызвать фильтр к этой функции нужно написать

<?php
add_filter( 'rest_pre_insert_user', 'rest_pre_insert_user_filter', 10, 2 );

В этом случаи функция rest_pre_insert_user_filter() будет принимать 2 аргумента и, после фильтрации, всегда возвращать первый из них.

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

Добавление полей. Экшены

Чисто технически, экшены и фильтры — одно и тоже, но логически разное. Экшен вместо того чтоб изменять данные, дополняет. Допустим, есть функция

<?php
function some_func( $args ) {
    // do something
    do_action( 'slug', $args );//вызов экшена
    // do something
}

аналогично фильтру, нужна экшн-функция и ее привязка именно на этот экшен

<?php
function action_func( $args ) {
    // do something with args
}
add_action( 'slug', 'action_func', 10, 1 );

Экшн-функция ничего не возвращает, в отличии от фильтра. Циферки при add_action имеют такой же смысл как и в add_filter.

Пример добавления дополнительного поля в запросе и схеме АПИ комментариев(взято в документации)

<?php
add_action( 'rest_api_init', function () {
    register_rest_field( 'comment', 'karma', array(
        'get_callback' => function( $comment_arr ) {
            $comment_obj = get_comment( $comment_arr['id'] );
            return (int) $comment_obj->comment_karma;
        },
        'update_callback' => function( $karma, $comment_obj ) {
            $ret = wp_update_comment( array(
                'comment_ID'    => $comment_obj->comment_ID,
                'comment_karma' => $karma
            ) );
            if ( false === $ret ) {
                return new WP_Error(
                  'rest_comment_karma_failed',
                  __( 'Failed to update comment karma.' ),
                  array( 'status' => 500 )
                );
            }
            return true;
        },
        'schema' => array(
            'description' => __( 'Comment karma.' ),
            'type'        => 'integer'
        ),
    ) );
} );

По-умолчанию,цифры равны 10 и 1.

Заключение

В этой статье проведено ознакомление с принципами изменения и дополнения WP REST API. Если нужно только работать с дефолтным АПИ — читайте. Для получения более подробной информации рекомендую прочесть REST API Handbook. Это, так сказать, информация из первых рук, т.е. разработчиков ВП АПИ.

Источники