Вопрос или проблема
Я использую последнюю версию плагина REST API в проекте и добавил свои маршруты в отдельное пространство имен (как рекомендовано в документации). JavaScript-клиент, включенный в API, автоматически создает модели и коллекции на основе публичных маршрутов в пространстве имен wp/v2
.
Кто-нибудь знает, возможно ли расширить встроенный клиент, чтобы использовать собственное пространство имен и автоматически разобрать корневую конечную точку для генерации моделей и коллекций для моего частного API?
Чтобы изменить префикс url rest, вы можете отфильтровать rest_url_prefix
. Но это лишь изменяет префикс /wp-json/
, который использует каждое пространство имен.
Попытка изменить wp/v2
означает модификацию плагина, и это пространство имен жестко закодировано в нескольких местах, таких как; WP_REST_Post_Statuses_Controller
.
Чтобы добавить свои пользовательские конечные точки, register_rest_route
встроен в ядро именно для этого.
<?php
add_action( 'rest_api_init', function () {
register_rest_route( 'your-namespace-here/v1', '/author/(?P<id>\d+)', array(
'methods' => 'GET',
'callback' => 'my_awesome_func',
) );
} );
Вам нужно будет подробнее изучить процесс открытия, чтобы увидеть, что автоматически создается для вашего API.
Пример шаблона контроллера для плагина WP REST API должен дать вам хорошую идею о том, что может входить в WP REST Controller.
add_action( 'rest_api_init', function () {
/*
* /wp-json/vendor/v1
*/
$routes = new Slug_Custom_Route();
$routes->register_routes();
});
class Slug_Custom_Route extends WP_REST_Controller {
/**
* Регистрация маршрутов для объектов контроллера.
*/
public function register_routes() {
$version = '1';
$namespace="vendor/v" . $version;
$base="route";
register_rest_route( $namespace, "https://wordpress.stackexchange.com/" . $base, array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => array(
),
),
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'create_item' ),
'permission_callback' => array( $this, 'create_item_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( true ),
),
) );
register_rest_route( $namespace, "https://wordpress.stackexchange.com/" . $base . '/(?P<id>[\d]+)', array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'args' => array(
'context' => array(
'default' => 'view',
),
),
),
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'update_item' ),
'permission_callback' => array( $this, 'update_item_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( false ),
),
array(
'methods' => WP_REST_Server::DELETABLE,
'callback' => array( $this, 'delete_item' ),
'permission_callback' => array( $this, 'delete_item_permissions_check' ),
'args' => array(
'force' => array(
'default' => false,
),
),
),
) );
register_rest_route( $namespace, "https://wordpress.stackexchange.com/" . $base . '/schema', array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_public_item_schema' ),
) );
}
/**
* Получить коллекцию элементов
*
* @param WP_REST_Request $request Полные данные о запросе.
* @return WP_Error|WP_REST_Response
*/
public function get_items( $request ) {
$items = array(); //выполните запрос, вызовите другой класс и т.д.
$data = array();
foreach( $items as $item ) {
$itemdata = $this->prepare_item_for_response( $item, $request );
$data[] = $this->prepare_response_for_collection( $itemdata );
}
return new WP_REST_Response( $data, 200 );
}
/**
* Получить один элемент из коллекции
*
* @param WP_REST_Request $request Полные данные о запросе.
* @return WP_Error|WP_REST_Response
*/
public function get_item( $request ) {
//получить параметры из запроса
$params = $request->get_params();
$item = array();//выполните запрос, вызовите другой класс и т.д.
$data = $this->prepare_item_for_response( $item, $request );
//вернуть ответ или ошибку на основе некоторых условий
if ( 1 == 1 ) {
return new WP_REST_Response( $data, 200 );
}else{
return new WP_Error( 'code', __( 'message', 'text-domain' ) );
}
}
/**
* Создать один элемент из коллекции
*
* @param WP_REST_Request $request Полные данные о запросе.
* @return WP_Error|WP_REST_Request
*/
public function create_item( $request ) {
$item = $this->prepare_item_for_database( $request );
if ( function_exists( 'slug_some_function_to_create_item') ) {
$data = slug_some_function_to_create_item( $item );
if ( is_array( $data ) ) {
return new WP_REST_Response( $data, 200 );
}
}
return new WP_Error( 'cant-create', __( 'message', 'text-domain'), array( 'status' => 500 ) );
}
/**
* Обновить один элемент из коллекции
*
* @param WP_REST_Request $request Полные данные о запросе.
* @return WP_Error|WP_REST_Request
*/
public function update_item( $request ) {
$item = $this->prepare_item_for_database( $request );
if ( function_exists( 'slug_some_function_to_update_item') ) {
$data = slug_some_function_to_update_item( $item );
if ( is_array( $data ) ) {
return new WP_REST_Response( $data, 200 );
}
}
return new WP_Error( 'cant-update', __( 'message', 'text-domain'), array( 'status' => 500 ) );
}
/**
* Удалить один элемент из коллекции
*
* @param WP_REST_Request $request Полные данные о запросе.
* @return WP_Error|WP_REST_Request
*/
public function delete_item( $request ) {
$item = $this->prepare_item_for_database( $request );
if ( function_exists( 'slug_some_function_to_delete_item') ) {
$deleted = slug_some_function_to_delete_item( $item );
if ( $deleted ) {
return new WP_REST_Response( true, 200 );
}
}
return new WP_Error( 'cant-delete', __( 'message', 'text-domain'), array( 'status' => 500 ) );
}
/**
* Проверьте, имеет ли данный запрос доступ к получению элементов
*
* @param WP_REST_Request $request Полные данные о запросе.
* @return WP_Error|bool
*/
public function get_items_permissions_check( $request ) {
//вернуть true; <--используйте, чтобы сделать доступным для всех
return current_user_can( 'edit_something' );
}
/**
* Проверьте, имеет ли данный запрос доступ к получению конкретного элемента
*
* @param WP_REST_Request $request Полные данные о запросе.
* @return WP_Error|bool
*/
public function get_item_permissions_check( $request ) {
return $this->get_items_permissions_check( $request );
}
/**
* Проверьте, имеет ли данный запрос доступ к созданию элементов
*
* @param WP_REST_Request $request Полные данные о запросе.
* @return WP_Error|bool
*/
public function create_item_permissions_check( $request ) {
return current_user_can( 'edit_something' );
}
/**
* Проверьте, имеет ли данный запрос доступ к обновлению конкретного элемента
*
* @param WP_REST_Request $request Полные данные о запросе.
* @return WP_Error|bool
*/
public function update_item_permissions_check( $request ) {
return $this->create_item_permissions_check( $request );
}
/**
* Проверьте, имеет ли данный запрос доступ к удалению конкретного элемента
*
* @param WP_REST_Request $request Полные данные о запросе.
* @return WP_Error|bool
*/
public function delete_item_permissions_check( $request ) {
return $this->create_item_permissions_check( $request );
}
/**
* Подготовьте элемент для операции создания или обновления
*
* @param WP_REST_Request $request Объект запроса
* @return WP_Error|object $prepared_item
*/
protected function prepare_item_for_database( $request ) {
return array();
}
/**
* Подготовьте элемент для REST-ответа
*
* @param mixed $item Представление элемента в WordPress.
* @param WP_REST_Request $request Объект запроса.
* @return mixed
*/
public function prepare_item_for_response( $item, $request ) {
return array();
}
/**
* Получите параметры запроса для коллекций
*
* @return array
*/
public function get_collection_params() {
return array(
'page' => array(
'description' => 'Текущая страница коллекции.',
'type' => 'integer',
'default' => 1,
'sanitize_callback' => 'absint',
),
'per_page' => array(
'description' => 'Максимальное количество элементов, которые будут возвращены в результате.',
'type' => 'integer',
'default' => 10,
'sanitize_callback' => 'absint',
),
'search' => array(
'description' => 'Ограничить результаты теми, которые соответствуют строке.',
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
),
);
}
}
И когда вы перейдете непосредственно к конечной точке пространства имен /wp-json/vendor/v1
, вы должны увидеть:
{
"namespace": "vendor\/v1",
"routes": {
"\/vendor\/v1": {
"namespace": "vendor\/v1",
"methods": ["GET"],
"endpoints": [{
"methods": ["GET"],
"args": {
"namespace": {
"required": false,
"default": "vendor\/v1"
},
"context": {
"required": false,
"default": "view"
}
}
}],
"_links": {
"self": "http:\/\/example.com\/wp-json\/vendor\/v1"
}
},
"\/vendor\/v1\/route": {
"namespace": "vendor\/v1",
"methods": ["GET", "POST", "GET", "POST"],
"endpoints": [{
"methods": ["GET"],
"args": []
}, {
"methods": ["POST"],
"args": []
}, {
"methods": ["GET"],
"args": []
}, {
"methods": ["POST"],
"args": []
}],
"_links": {
"self": "http:\/\/example.com\/wp-json\/vendor\/v1\/route"
}
},
"\/vendor\/v1\/route\/(?P<id>[\\d]+)": {
"namespace": "vendor\/v1",
"methods": ["GET", "POST", "PUT", "PATCH", "DELETE", "GET", "POST", "PUT", "PATCH", "DELETE"],
"endpoints": [{
"methods": ["GET"],
"args": {
"context": {
"required": false,
"default": "view"
}
}
}, {
"methods": ["POST", "PUT", "PATCH"],
"args": []
}, {
"methods": ["DELETE"],
"args": {
"force": {
"required": false,
"default": false
}
}
}, {
"methods": ["GET"],
"args": {
"context": {
"required": false,
"default": "view"
}
}
}, {
"methods": ["POST", "PUT", "PATCH"],
"args": []
}, {
"methods": ["DELETE"],
"args": {
"force": {
"required": false,
"default": false
}
}
}]
},
"\/vendor\/v1\/route\/schema": {
"namespace": "vendor\/v1",
"methods": ["GET", "GET"],
"endpoints": [{
"methods": ["GET"],
"args": []
}, {
"methods": ["GET"],
"args": []
}],
"_links": {
"self": "http:\/\/example.com\/wp-json\/vendor\/v1\/route\/schema"
}
}
},
"_links": {
"up": [{
"href": "http:\/\/example.com\/wp-json\/"
}]
}
}
Две основные вещи, которые выделяются:
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE )
и
'args' => array (
'force' => array (
'default' => false,
),
),
Еще один пример передачи $args
в register_rest_route
, который будет отображаться в корне пространства имен:
register_rest_route( "{$root}/{$version}", '/products', array(
array(
'methods' => \WP_REST_Server::READABLE,
'callback' => array( $cb_class, 'get_items' ),
'args' => array(
'per_page' => array(
'default' => 10,
'sanitize_callback' => 'absint',
),
'page' => array(
'default' => 1,
'sanitize_callback' => 'absint',
),
'soon' => array(
'default' => 0,
'sanitize_callback' => 'absint',
),
'slug' => array(
'default' => false,
'sanitize_callback' => 'sanitize_title',
)
),
'permission_callback' => array( $this, 'permissions_check' )
),
)
);
Смотрим немного дальше, get_data_for_route
используется для сбора того, что в конечном итоге попадает в конечные точки открытия, и это можно фильтровать с помощью rest_endpoints_description
или get_data_for_route
. Поэтому, в теории, если по умолчанию это не дает вам то, что вы хотите, вы можете использовать этот фильтр, чтобы добавить свои модели и/или коллекции для вашего частного API.
Итак, собирая все вместе и переопределяя автоматические конечные точки:
function my_awesome_func( WP_REST_Request $request) {
return "awesome! " . $request->get_param( 'id' );
}
add_action( 'rest_api_init', function() {
register_rest_route( 'my-thing/v1', '/awesome/(?P<id>\d+)', array (
'methods' => 'GET',
'callback' => 'my_awesome_func',
) );
} );
add_filter( 'rest_endpoints_description', function( $data ) {
if ( $data[ 'namespace' ] === 'my-thing/v1' ) {
$data[ 'endpoints' ] = array (
'foo' => 'bar',
'my' => 'custom-api',
'awesome' => 'it really is',
);
}
return $data;
} );
/*
{
"namespace": "my-thing\/v1",
"routes": {
"\/my-thing\/v1": {
"namespace": "my-thing\/v1",
"methods": ["GET"],
"endpoints": {
"foo": "bar",
"my": "custom-api",
"awesome": "it really is"
},
"_links": {
"self": "http:\/\/example.com\/wp-json\/my-thing\/v1"
}
},
"\/my-thing\/v1\/awesome\/(?P<id>\\d+)": {
"namespace": "my-thing\/v1",
"methods": ["GET"],
"endpoints": {
"foo": "bar",
"my": "custom-api",
"awesome": "it really is"
}
}
},
"_links": {
"up": [{
"href": "http:\/\/example.com\/wp-json\/"
}]
}
}
*/
Ответ или решение
Использование REST API (v2) JavaScript клиента для работы с маршрутами в закрытом пространстве имен
Введение
REST API в WordPress обеспечивает разработчикам мощный инструмент для создания и управления данными на их сайте. Особенно важно, что начиная с последних версий, вы можете расширять API, добавляя маршруты в собственное пространство имен. Это особенно полезно, когда вы хотите создать закрытые или специализированные API, доступные только определенным пользователям или приложениям.
Создание маршрутов в собственном пространстве имен
Чтобы зарегистрировать новые маршруты в вашем пространстве имен, вы можете воспользоваться хук-методом rest_api_init
. Например, для добавления маршрута, который возвращает информацию о пользователе по его идентификатору, воспользуйтесь следующим кодом:
add_action('rest_api_init', function () {
register_rest_route('your-namespace/v1', '/author/(?P<id>\d+)', array(
'methods' => 'GET',
'callback' => 'my_awesome_func',
));
});
В этом примере your-namespace
является заменой для вашего конкретного пространства имен, а my_awesome_func
– это функция, которая будет вызываться при обращении к данному маршруту.
Расширение JavaScript клиента
По умолчанию JavaScript клиент REST API автоматически создает модели и коллекции на основе публичных маршрутов в пространстве имен wp/v2
. Однако, для работы с вашими закрытыми маршрутами, вы должны вручную настроить клиент, чтобы он мог использовать ваше пользовательское пространство имен.
- Создание клиента с кастомизацией пространства имен: Для инициализации вашего клиента вам необходимо создать экземпляр объекта, который будет указывать на ваше пространство имен:
const customApi = wp.apiFetch({ path: '/wp-json/your-namespace/v1' });
- Автоматический парсинг корневого конечного пункта: Для автоматического создания моделей и коллекций необходимо вручную настроить клиент, чтобы он мог обрабатывать данные, возвращаемые вашим API. Один из способов – создать функции, которые будут запрашивать данные и формировать модели.
Обработка разрешений и маршрутов
Важно учитывать, что при работе с закрытыми маршрутами необходимо управлять разрешениями пользователей. Например, вы можете использовать permission_callback
, чтобы убедиться, что только авторизованные пользователи смогут получить доступ к определенным маршрутам:
add_action('rest_api_init', function () {
register_rest_route('your-namespace/v1', '/author/(?P<id>\d+)', array(
'methods' => 'GET',
'callback' => 'my_awesome_func',
'permission_callback' => function () {
return current_user_can('read'); // Пример разрешений
}
));
});
Использование фильтров и хуков
Если вы хотите расширить стандартные эндпоинты, вы можете использовать фильтры, такие как rest_endpoints_description
, чтобы изменять данные, которые возвращаются при обращении к вашему пространству имен.
Вот пример применения фильтра:
add_filter('rest_endpoints_description', function ($data) {
if ($data['namespace'] === 'your-namespace/v1') {
$data['endpoints'] = array(
'foo' => 'bar',
'custom' => 'api description',
);
}
return $data;
});
Заключение
В этом ответе мы рассмотрели, как использовать REST API в WordPress для работы с собственными маршрутами в закрытых пространствах имен. Мы обсудили регистрацию маршрутов, настройку клиента JavaScript, а также управление разрешениями. Это знание позволит вам создавать более безопасные и адаптированные API, которые отвечают потребностям вашего проекта и пользовательской базы.
Эти шаги дадут возможность значительно расширить функционал вашего приложения и улучшить взаимодействие с данными, поддерживая актуальные современные требования к разработке веб-приложений.