Класи контролерів

У цьому розділі описані всі класи 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_Controller .

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *