Классы контроллеров

В этом разделе описаны все PHP классы, которые используются в WP API. Они делятся на два типа: Классы инфраструктуры и Классы контроллеров (маршрутов, конечных точек, ресурсов).

Общие сведения

В WP API используется принцип Model-View-Controller — это стандартная модель в разработке приложений. Суть этой модели в том, что логика приложения разделена таким образом, что можно изменять одну его часть, так чтобы изменения не повлияли на другую его часть.

По такой схеме, логика кода WP-API разделена на независимые компоненты (классы):

  • Класс получения запроса.
  • Классы контроллеров (обработка запросы и создание ответа).
  • Класс ответа на запрос (получает подготовленный контроллером ответ и формирует его в стандартный JSON вид, устанавливает заголовки ответа и код ответа).

Класс запроса и ответа - это Классы инфраструктуры WP API, с ними как правило работать не нужно. Мы, разработчики, почти всегда будем работать с Классом контроллера.

Задача Класса контроллера объединить отдельные части обработки запроса API в единый механизм. В них должны создаваться маршруты, они получают запросы обрабатывают их и генерируют ответы. Так же там описывается схема ресурса.

Концепция контроллера принята в рамках WP-API для того, чтобы иметь стандартный шаблон для Классов контроллера - классов представляющих ресурсы (конечные точки). Шаблоном класса контроллера является абстрактный класс WP_REST_Controller. Каждый класс контроллера должен иметь аналогичную схему методов, сделано так для того, чтобы все конечные точки имели одинаковые названия PHP методов.

Классы инфраструктуры и Классы контроллеров

Классы инфраструктуры дополняют классы контроллеров - они обрабатывают логику API, но не выполняют преобразований данных. Также и классы контроллеров (конечных точек) инкапсулируют логику необходимую для выполнения CRUD операций с ресурсами, но не связаны с логикой инфраструктуры. Это и есть упомянутая выше модель «Model-View-Controller».

К классам инфраструктуры относятся:

  • WP_REST_Server — фундаментальный контроллер REST API, который обслуживает запрос. При любом запросе к API сначала вызывается WP_REST_Server и определяет, какой запрашивается маршрут, а затем передает коллбэк маршрута в объект WP_REST_Request. WP_REST_Server также выполняет проверку аутентификации и проверки валидации и доступа (validation и permissions).
  • WP_REST_Request — этот объект содержит сведения о запросе, такие как заголовки запроса, параметры и метод, а также сам маршрут. Он также может проверять и очищать запрос (validation и sanitization).
  • WP_REST_Response — отвечает за ответ на запрос. Устанавливает заголовки ответа, статус, тело ответа (JSON). Предоставляет вспомогательные методы add_link() (добавляет связанные с ответом ссылки) и query_navigation_headers() (данные навигации в заголовках).

К классам контроллеров относятся:

Пример регистрации маршрута через Класс контроллера

Подробное описание этого примера смотрите в разделе Создание маршрутов.

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;
	}

}

Принципы Классов контроллеров в WP API

Классы контроллеров решают две проблемы при разработке эндпоинтов:

  • Отсутствие пространств имен — WordPress не использует пространства имён PHP (для поддержки PHP 5.2). Оборачивание группы PHP функций которые описывают конечную точку в класс, позволяет избежать конфликтов с названиями функций. Например, мы назвали функцию get_items() и другой разработчик сделал тоже самое, в результате сайт перестанет работать из-за фатальной ошибки PHP.
  • Согласование структур — Один раз разобравшись со структурой класса контроллера, разработчики смогут быстро понимать структуру класса контроллера, которую писал другой человек.

Для классов контроллеров разработчиками создан специальный шаблон: абстрактный класс WP_REST_Controller. На его основе рекомендуется создавать свои контроллеры маршрутов. Так например, все классы конечных точек WP расширяют WP_REST_Controller, например:

class WP_REST_Posts_Controller extends WP_REST_Controller {
	// ...
}

Методы класса-шаблона WP_REST_Controller:

Метод Описание метода
register_routes() вызывается при первом создания экземпляра класса и регистрирует маршруты.
get_items() получает коллекцию ресустов (постов, рубрик и т.д.).
get_item() получает отдельный ресурс (пост, рубрику и т.д.). Если ресурса не существует, будет возвращен HTTP статус 404. Если нет права просматривать ресурс, то статус будет 403.
create_item() создает новый ресурс. Если создание прошло успешно, то WP_REST_Response вернет HTTP статус 201 и ссылку на созданный ресурс. Если создание не удалось, то будет возвращен соответствующий код ошибки в HTTP заголовке.
update_item() обновляет существующий ресурс.
delete_item() удаляет существующий ресурс. Если удаление не удалось, то будет возвращен соответствующий код ошибки в HTTP заголовке.
get_items_permissions_check() перед вызовом коллбэк функции проверяет есть ли право у текущего запроса получать коллекцию ресурсов.
get_item_permissions_check() перед вызовом коллбэк функции проверяет есть ли право у текущего запроса получать отдельный ресурс.
create_item_permissions_check() перед вызовом коллбэк функции проверяет есть ли право у текущего запроса создавать ресурс.
update_item_permissions_check() перед вызовом коллбэк функции проверяет есть ли право у текущего запроса обновлять ресурс.
delete_item_permissions_check() перед вызовом коллбэк функции проверяет есть ли право у текущего запроса удалять ресурс.
prepare_item_for_response() изменяет данные ресурса, чтобы они подходили под схему ответа.
prepare_response_for_collection() prepare_item_for_response() возвращает объект WP_REST_Response. Это вспомогательная функция, которая собирает такие ответы в общую коллекцию ресурсов и возвращает эту коллекцию в качестве ответа.
filter_response_by_context() фильтрует данные ресурса в соответствии с указанным параметром context.
get_item_schema() получает объект схемы ресурса.

Заметка про наследование классов

Не следует злоупотреблять наследованием классов. Например: если мы написали класс контроллера для эндпоинта записей (пример выше) и хотим также поддерживать произвольные типы записей, то не следует расширять свой класс My_REST_Posts_Controller следующим образом:

class My_CPT_REST_Controller extends My_REST_Posts_Controller {
	...
}

Вместо этого нужно либо создать отдельный класс контроллера, либо заставить My_REST_Posts_Controller обрабатывать все доступные типы записей. Дело в том, что родительский класс (тот который наследуется) может измениться, а наши подклассы зависят от него, в результате получаем головную боль. Поэтому, если нужна общая структура для классов, необходимо создать свой базовый класс контроллера в виде интерфейса или абстрактного класса, а затем использовать его как основу для подклассов. Такой подход абстрактного класса используется во многих конечных точках REST API WordPress. Например, классы WP_REST_Posts_Controller, WP_REST_Terms_Controller и т.д. расширяют абстрактный класс WP_REST_Controller.