Создание Маршрутов
О том что такое Маршруты и Эндпоинты мы уже говорили, а в этом разделе рассмотрим, как создавать свои маршруты, у которых будут свои конечные точки.
Создание маршрутов основывается всего на одной функции register_rest_route(). По сути, все что нужно, чтобы создать свой маршрут и конечные точки - это разобраться как пользоваться этой функцией (смотрите её описание).
Ниже мы рассмотрим простой пример создания маршрута, а затем расширим его до создания маршрута на основе класса контроллера.
Простой пример создания маршрута
Начнем с создания простой PHP функции
## Получает посты указанного автора
function my_awesome_func( WP_REST_Request $request ){
$posts = get_posts( array(
'author' => (int) $request['id'],
) );
if ( empty( $posts ) )
return new WP_Error( 'no_author_posts', 'Записей не найдено', [ 'status' => 404 ] );
return $posts;
}
Чтобы результат работы этой функции стал доступен из API, нам нужно зарегистрировать маршрут и прикрепить к нему эту функцию. Тогда по запросу к этому маршруту, пользователь увидит ответ этой функции.
Маршрут регистрируется функцией register_rest_route(), на хуке rest_api_init, чтобы не выполнять никаких операций, если REST API отключен.
В функцию register_rest_route() нужно передать три параметра: пространство имен, маршрут и опции маршрута (в опциях указываем название нашей php функции).
add_action( 'rest_api_init', function(){
register_rest_route( 'myplugin/v1', '/author-posts/(?P<id>\d+)', [
'methods' => 'GET',
'callback' => 'my_awesome_func',
] );
} );
Почему пространства имен называется именно myplugin/v1, читайте здесь.
Маршрут /author-posts/(?P<id>\d+) указан как регулярное выражение, потому что мы хотим, чтобы по URL /author-posts/{id} нам выдавались посты автора указанного вместо {id}.
Теперь пройдя по ссылке http://example.com/wp-json/myplugin/v1/author-posts/1 на нашем сайте мы увидим JSON ответ, который вернула наша функция my_awesome_func().
GET запрос на URL http://example.com/wp-json/myplugin/v1/author-posts/1 - это конечная точка, которую мы описали при регистрации маршрута.
Маршрут может иметь много конечных точек, и для каждой конечной точки можно указать разные параметры:
- methods — HTTP методы по которым нужно обращаться к конечной точке
- callback — функцию обратного вызова для ответа на запрос
- permission_callback — функцию обратного вызова для проверки права доступа к конечной точке.
- args — параметры запроса, которые можно передать конечной точке. Каждый параметр описывается в виде элемента массива. Для каждого параметра можно указать свои опции, например, обязательный ли это параметр (required) или значение параметра по умолчанию (default), функцию проверки значения параметра (validate_callback).
Подробнее обо всех параметрах и аргументах читайте в описании функции register_rest_route().
Чтобы было понятнее, давайте расширим наш маршрут и добавим ему еще одну конечную точку, к которой может обратиться только авторизованный пользователь и только POST запросом, и которой можно передать два параметра: arg_str и arg_int.
add_action( 'rest_api_init', function(){
register_rest_route( 'myplugin/v1', '/author-posts/(?P<id>\d+)', array(
array(
'methods' => 'GET',
'callback' => 'my_awesome_func',
),
array(
'methods' => 'POST',
'callback' => 'my_awesome_func_two',
'args' => array(
'arg_str' => array(
'type' => 'string', // значение параметра должно быть строкой
'required' => true, // параметр обязательный
),
'arg_int' => array(
'type' => 'integer', // значение параметра должно быть числом
'default' => 10, // значение по умолчанию = 10
),
),
'permission_callback' => function( $request ){
// только авторизованный юзер имеет доступ к эндпоинту
return is_user_logged_in();
},
// или в данном случае можно записать проще
'permission_callback' => 'is_user_logged_in',
)
) );
} );
В этом примере для второй конечной точки (POST) используется функция my_awesome_func_two она должна вернуть ответ, если пользователь авторизован и указан обязательный параметр arg_str. Также она получит два параметра запроса. Функции my_awesome_func_two() у нас еще нет, поэтому давайте её создадим, пусть она в ответ на запрос вернет переданные параметры запроса:
function my_awesome_func_two( WP_REST_Request $request ){
$response = array(
'arg_str' => $request->get_param('arg_str'),
'arg_int' => $request->get_param('arg_int')
);
return $response;
}
Проверка созданного маршрута
Теперь проверим, как все работает, попробуем отправить запросы на наши эндпоинты.
Отправляем GET запрос:
GET http://example.com/wp-json/myplugin/v1/author-posts/1
Получим ответ:
[
{
"ID": 72,
"post_author": "1",
"post_content": "Контент записи",
"post_title": "Заголовок записи",
...
"comment_count": "0",
},
{ ... },
{ ... }
]
Отправляем POST запрос:
POST http://example.com/wp-json/myplugin/v1/author-posts/1
Получаем ошибку, потому что мы не авторизованы:
{
"code": "empty_username",
"message": "<strong>ERROR</strong>: The username field is empty.",
"data": null,
"additional_errors": [
{
"code": "empty_password",
"message": "<strong>ERROR</strong>: The password field is empty.",
"data": null
}
]
}
Авторизуемся, отправляем запрос еще раз, и получаем ошибку, потому что мы не указали обязательный параметр 'arg_str'.
{
"code": "rest_missing_callback_param",
"message": "Отсутствует параметр: arg_str",
"data": {
"status": 400,
"params": [
"arg_str"
]
}
}
Укажем параметры
POST http://example.com/wp-json/myplugin/v1/author-posts/1?arg_str=foo&arg_int=bar
Опять получаем ошибку, потому что тип параметра arg_int у нас указан integer (число), а мы указали строку:
{
"code": "rest_invalid_param",
"message": "Неверный параметр: arg_int",
"data": {
"status": 400,
"params": {
"arg_int": "arg_int не принадлежит к типу integer."
}
}
}
Поправим значение:
POST http://example.com/wp-json/myplugin/v1/author-posts/1?arg_str=foo&arg_int=123
Получаем ожидаемый ответ:
{
"arg_str": "foo",
"arg_int": 123
}
Пояснения к аргументам при создании маршрута
Смотрите описание функции register_rest_route().
Создание маршрута на основе Класса контроллера (ООП)
Подробнее про Классы контроллеров читайте в одноименном разделе.
При регистрации нового маршрута и его конечных точек рекомендуется использовать класс контроллера, потому что он делает код более удобным, стабильным и легко расширяемым.
Здесь все по сути также, в основе лежит функция register_rest_route().
Суть создания класса контроллера в удобности кода и его расширяемости — в том чтобы создать один класс, который будет работать с одним ресурсом. Но может иметь несколько маршрутов, разные параметры у маршрутов, единую схему ресурса (которую кстати можно описать при создании маршрута) и многое другое.
Т.е. создавая класс контроллера, мы также, как и в примере выше, регистрируем новые маршруты, но при этом создаем логичный, расширяемый код на основе ООП.
Каким должен быть класс контроллер продумано разработчиками WP и описано в абстрактном классе WP_REST_Controller
В задачи контроллера входит:
- Получение входных данных.
- Генерация выходных данных.
В WP REST API наши контроллеры будут получать запрос как объект WP_REST_Request и могут генерировать любой ответ, в частности для удобства можно отдавать ответ в виде объекта WP_REST_Response (см. rest_ensure_response()).
Пример создания Класса контроллера
GitHub// Запускаем наш контроллер и регистрируем маршруты
add_action( 'rest_api_init', 'prefix_register_my_rest_routes' );
function prefix_register_my_rest_routes() {
$controller = new My_REST_Posts_Controller();
$controller->register_routes();
}
class My_REST_Posts_Controller extends WP_REST_Controller {
function __construct(){
$this->namespace = 'my-namespace/v1';
$this->rest_base = 'posts';
}
function register_routes(){
register_rest_route( $this->namespace, "/$this->rest_base", [
[
'methods' => 'GET',
'callback' => [ $this, 'get_items' ],
'permission_callback' => [ $this, 'get_items_permissions_check' ],
],
'schema' => [ $this, 'get_item_schema' ],
] );
register_rest_route( $this->namespace, "/$this->rest_base/(?P<id>[\w]+)", [
[
'methods' => 'GET',
'callback' => [ $this, 'get_item' ],
'permission_callback' => [ $this, 'get_item_permissions_check' ],
],
'schema' => [ $this, 'get_item_schema' ],
] );
}
function get_items_permissions_check( $request ){
if ( ! current_user_can( 'read' ) )
return new WP_Error( 'rest_forbidden', esc_html__( 'You cannot view the post resource.' ), [ 'status' => $this->error_status_code() ] );
return true;
}
/**
* Получает последние посты и отдает их в виде rest ответа.
*
* @param WP_REST_Request $request Текущий запрос.
*
* @return WP_Error|array
*/
function get_items( $request ){
$data = [];
$posts = get_posts( [
'post_per_page' => 5,
] );
if ( empty( $posts ) )
return $data;
foreach( $posts as $post ){
$response = $this->prepare_item_for_response( $post, $request );
$data[] = $this->prepare_response_for_collection( $response );
}
return $data;
}
## Проверка права доступа.
function get_item_permissions_check( $request ){
return $this->get_items_permissions_check( $request );
}
/**
* Получает отдельный ресурс.
*
* @param WP_REST_Request $request Текущий запрос.
*
* @return array
*/
function get_item( $request ){
$id = (int) $request['id'];
$post = get_post( $id );
if( ! $post )
return array();
return $this->prepare_item_for_response( $post, $request );
}
/**
* Собирает данные ресурса в соответствии со схемой ресурса.
*
* @param WP_Post $post Объект ресурса, из которого будут взяты оригинальные данные.
* @param WP_REST_Request $request Текущий запрос.
*
* @return array
*/
function prepare_item_for_response( $post, $request ){
$post_data = [];
$schema = $this->get_item_schema();
// We are also renaming the fields to more understandable names.
if ( isset( $schema['properties']['id'] ) )
$post_data['id'] = (int) $post->ID;
if ( isset( $schema['properties']['content'] ) )
$post_data['content'] = apply_filters( 'the_content', $post->post_content, $post );
return $post_data;
}
/**
* Подготавливает ответ отдельного ресурса для добавления его в коллекцию ресурсов.
*
* @param WP_REST_Response $response Response object.
*
* @return array|mixed Response data, ready for insertion into collection data.
*/
function prepare_response_for_collection( $response ){
if ( ! ( $response instanceof WP_REST_Response ) ){
return $response;
}
$data = (array) $response->get_data();
$server = rest_get_server();
if ( method_exists( $server, 'get_compact_response_links' ) ){
$links = call_user_func( [ $server, 'get_compact_response_links' ], $response );
}
else {
$links = call_user_func( [ $server, 'get_response_links' ], $response );
}
if ( ! empty( $links ) ){
$data['_links'] = $links;
}
return $data;
}
## Схема ресурса.
function get_item_schema(){
$schema = [
// показывает какую версию схемы мы используем - это draft 4
'$schema' => 'http://json-schema.org/draft-04/schema#',
// определяет ресурс который описывает схема
'title' => 'vehicle',
'type' => 'object',
// в JSON схеме нужно указывать свойства в атрибуете 'properties'.
'properties' => [
'id' => [
'description' => 'Unique identifier for the object.',
'type' => 'integer',
'context' => [ 'view', 'edit', 'embed' ],
'readonly' => true,
],
'vin' => [
'description' => 'VIN code of vehicle.',
'type' => 'string',
],
// TODO добавить поля
// []
],
];
return $schema;
}
## Устанавливает HTTP статус код для авторизации.
function error_status_code(){
return is_user_logged_in() ? 403 : 401;
}
}
Еще примеры Классов контроллера, смотрите в ядре WordPress. Вот некоторые из них: