Создание Маршрутов
О том что такое Маршруты и Эндпоинты мы уже говорили, а в этом разделе рассмотрим, как создавать свои маршруты, у которых будут свои конечные точки.
Создание маршрутов основывается всего на одной функции 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. Вот некоторые из них: