WordPress как на ладони
rgbcode is looking for WordPress developers. Очень Удобный и Быстрый Хостинг для сайтов на WordPress. Пользуюсь сам и вам рекомендую!

Создание Маршрутов

О том что такое Маршруты и Эндпоинты мы уже говорили, а в этом разделе рассмотрим, как создавать свои маршруты, у которых будут свои конечные точки.

Создание маршрутов основывается всего на одной функции 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. Вот некоторые из них:

9 комментариев
    Войти