WP_REST_Controller 类继承体系与接口定义
大家好,今天我们来深入探讨 WordPress REST API 中 WP_REST_Controller
类的继承体系以及它所定义的接口。WP_REST_Controller
是构建自定义 REST API 端点的核心类,理解它的结构和功能对于开发健壮、可维护的 API 至关重要。
1. WP_REST_Controller
的角色和职责
WP_REST_Controller
本身是一个抽象类,它的主要职责是:
- 定义通用的 API 端点注册和处理逻辑: 它提供了一些基础方法,用于注册路由、处理请求参数、执行权限验证等。
- 提供钩子和过滤器: 允许开发者在 API 请求的不同阶段进行自定义操作。
- 作为自定义控制器类的基类: 鼓励开发者通过继承
WP_REST_Controller
来构建自己的 API 端点逻辑,实现代码复用和模块化。
2. 继承体系
WP_REST_Controller
并没有直接的父类,它是 WordPress REST API 框架中的一个基类。然而,我们通常会继承它来实现特定的 API 控制器。一个常见的例子是 WP_REST_Posts_Controller
,它负责处理文章相关的 API 请求。
以下是一个简单的继承关系示例:
Object
└─ WP_REST_Controller
└─ WP_REST_Posts_Controller
└─ Your_Custom_Posts_Controller
在这个例子中,Your_Custom_Posts_Controller
继承了 WP_REST_Posts_Controller
,而 WP_REST_Posts_Controller
又继承了 WP_REST_Controller
。这允许 Your_Custom_Posts_Controller
继承 WP_REST_Posts_Controller
已有的文章相关逻辑,并在此基础上进行定制。
3. 关键方法和属性
WP_REST_Controller
类定义了一些重要的属性和方法,这些属性和方法在子类中经常被使用和重写。
3.1 属性
$namespace
: (string) API 的命名空间,例如'my-plugin/v1'
。$rest_base
: (string) API 的路由基地址,例如'posts'
。
3.2 方法
以下是一些常用的方法,以及它们的作用:
方法名 | 描述 |
---|---|
register_routes() |
必须实现。 用于注册 API 的路由。在这个方法中,你需要使用 register_rest_route() 函数来定义你的 API 端点。 |
get_item( $request ) |
用于获取单个资源。例如,获取 ID 为 1 的文章。 |
get_items( $request ) |
用于获取资源列表。例如,获取所有文章。 |
create_item( $request ) |
用于创建新的资源。例如,创建一个新的文章。 |
update_item( $request ) |
用于更新已有的资源。例如,更新 ID 为 1 的文章。 |
delete_item( $request ) |
用于删除资源。例如,删除 ID 为 1 的文章。 |
prepare_item_for_response( $item, $request ) |
用于格式化资源数据,使其符合 API 响应的格式。这个方法允许你添加额外的字段、转换数据类型等。 |
get_item_schema() |
用于定义 API 资源的 Schema。Schema 描述了资源的结构、字段类型、验证规则等。这对于 API 的文档生成和客户端的自动验证非常有用。 |
get_collection_params() |
用于定义 API 集合端点的参数。例如,可以定义 per_page 参数来控制每页返回的资源数量。 |
get_endpoint_args_for_item_schema( $method = WP_REST_Server::READABLE ) |
根据 Schema 获取端点参数。这可以用于验证请求参数是否符合 Schema 定义。 |
check_permission( $request ) |
检查当前用户是否有权限访问 API 端点。默认情况下,所有用户都可以访问,你需要根据你的需求重写这个方法。通常用于细粒度权限控制。 |
get_public_item_schema() |
获取公开的item schema, 默认返回 get_item_schema() , 可以重写以隐藏敏感信息 |
4. 代码示例:自定义文章类型 API 控制器
让我们创建一个简单的自定义文章类型 API 控制器。假设我们有一个名为 book
的自定义文章类型。
<?php
/**
* 自定义文章类型 "book" 的 REST API 控制器.
*/
class WP_REST_Books_Controller extends WP_REST_Controller {
/**
* 构造函数.
*/
public function __construct() {
$this->namespace = 'my-plugin/v1';
$this->rest_base = 'books';
}
/**
* 注册路由.
*/
public function register_routes() {
register_rest_route( $this->namespace, '/' . $this->rest_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( WP_REST_Server::CREATABLE ),
),
) );
register_rest_route( $this->namespace, '/' . $this->rest_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( WP_REST_Server::EDITABLE ),
),
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,
'type' => 'boolean',
'description' => __( '是否强制删除,不放入回收站。', 'my-plugin' ),
),
),
),
) );
register_rest_route( $this->namespace, '/' . $this->rest_base . '/schema', array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item_schema' ),
'permission_callback' => '__return_true',
),
) );
}
/**
* 检查获取文章列表的权限.
*
* @param WP_REST_Request $request Full details about the request.
* @return true|WP_Error True if the request has read access, WP_Error object otherwise.
*/
public function get_items_permissions_check( $request ) {
if ( ! current_user_can( 'read' ) ) {
return new WP_Error( 'rest_forbidden', __( '抱歉,您没有权限查看文章列表。', 'my-plugin' ), array( 'status' => $this->authorization_status_code() ) );
}
return true;
}
/**
* 获取文章列表.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error
*/
public function get_items( $request ) {
$args = array(
'post_type' => 'book',
'posts_per_page' => 10, // 可以从 $request->get_param( 'per_page' ) 获取
);
$query = new WP_Query( $args );
$data = array();
foreach ( $query->posts as $post ) {
$post_data = $this->prepare_item_for_response( $post, $request );
$data[] = $this->prepare_response_for_collection( $post_data );
}
return rest_ensure_response( $data );
}
/**
* 检查获取单个文章的权限.
*
* @param WP_REST_Request $request Full details about the request.
* @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise.
*/
public function get_item_permissions_check( $request ) {
return $this->get_items_permissions_check( $request ); // 复用列表权限
}
/**
* 获取单个文章.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error
*/
public function get_item( $request ) {
$id = (int) $request['id'];
$post = get_post( $id );
if ( empty( $post ) || $post->post_type !== 'book' ) {
return new WP_Error( 'rest_not_found', __( '找不到该文章。', 'my-plugin' ), array( 'status' => 404 ) );
}
$data = $this->prepare_item_for_response( $post, $request );
return rest_ensure_response( $data );
}
/**
* 检查创建文章的权限.
*
* @param WP_REST_Request $request Full details about the request.
* @return true|WP_Error True if the request has the correct access, WP_Error object otherwise.
*/
public function create_item_permissions_check( $request ) {
if ( ! current_user_can( 'publish_posts' ) ) {
return new WP_Error( 'rest_forbidden', __( '抱歉,您没有权限创建文章。', 'my-plugin' ), array( 'status' => $this->authorization_status_code() ) );
}
return true;
}
/**
* 创建文章.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error
*/
public function create_item( $request ) {
$prepared_post = $this->prepare_item_for_database( $request );
if ( is_wp_error( $prepared_post ) ) {
return $prepared_post;
}
$post_id = wp_insert_post( $prepared_post, true );
if ( is_wp_error( $post_id ) ) {
return $post_id;
}
if ( ! empty( $post_id ) ) {
$post = get_post( $post_id );
$this->update_additional_fields_for_object( $post, $request );
wp_update_post( (array) $post );
}
$post = get_post( $post_id );
$data = $this->prepare_item_for_response( $post, $request );
return new WP_REST_Response( $data, 201 ); // 201 Created
}
/**
* 检查更新文章的权限.
*
* @param WP_REST_Request $request Full details about the request.
* @return true|WP_Error True if the request has access to update the item, WP_Error object otherwise.
*/
public function update_item_permissions_check( $request ) {
if ( ! current_user_can( 'edit_posts' ) ) {
return new WP_Error( 'rest_forbidden', __( '抱歉,您没有权限编辑文章。', 'my-plugin' ), array( 'status' => $this->authorization_status_code() ) );
}
return true;
}
/**
* 更新文章.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error
*/
public function update_item( $request ) {
$id = (int) $request['id'];
$post = get_post( $id );
if ( empty( $post ) || $post->post_type !== 'book' ) {
return new WP_Error( 'rest_not_found', __( '找不到该文章。', 'my-plugin' ), array( 'status' => 404 ) );
}
$prepared_post = $this->prepare_item_for_database( $request );
if ( is_wp_error( $prepared_post ) ) {
return $prepared_post;
}
$prepared_post['ID'] = $id; // 确保 ID 被设置
$post_id = wp_update_post( $prepared_post, true );
if ( is_wp_error( $post_id ) ) {
return $post_id;
}
$post = get_post( $post_id );
$data = $this->prepare_item_for_response( $post, $request );
return rest_ensure_response( $data );
}
/**
* 检查删除文章的权限.
*
* @param WP_REST_Request $request Full details about the request.
* @return true|WP_Error True if the request has access to delete the item, WP_Error object otherwise.
*/
public function delete_item_permissions_check( $request ) {
if ( ! current_user_can( 'delete_posts' ) ) {
return new WP_Error( 'rest_forbidden', __( '抱歉,您没有权限删除文章。', 'my-plugin' ), array( 'status' => $this->authorization_status_code() ) );
}
return true;
}
/**
* 删除文章.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error
*/
public function delete_item( $request ) {
$id = (int) $request['id'];
$post = get_post( $id );
if ( empty( $post ) || $post->post_type !== 'book' ) {
return new WP_Error( 'rest_not_found', __( '找不到该文章。', 'my-plugin' ), array( 'status' => 404 ) );
}
$force = isset( $request['force'] ) ? (bool) $request['force'] : false;
$result = wp_delete_post( $id, $force );
if ( ! $result ) {
return new WP_Error( 'rest_cannot_delete', __( '无法删除该文章。', 'my-plugin' ), array( 'status' => 500 ) );
}
$data = array(
'deleted' => true,
'previous' => $this->prepare_item_for_response( $post, $request ),
);
return rest_ensure_response( $data );
}
/**
* 准备文章数据以进行响应.
*
* @param WP_Post $post 文章对象.
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response
*/
public function prepare_item_for_response( $post, $request ) {
$data = array(
'id' => $post->ID,
'title' => get_the_title( $post ),
'content' => apply_filters( 'the_content', $post->post_content ),
'status' => $post->post_status,
'author' => $post->post_author,
'meta' => get_post_meta( $post->ID ),
);
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
// Wrap the data in a response object.
$response = rest_ensure_response( $data );
$response->add_links( $this->prepare_links( $post ) );
return apply_filters( 'rest_prepare_book', $response, $post, $request );
}
/**
* 准备文章数据以保存到数据库.
*
* @param WP_REST_Request $request Full details about the request.
* @return array|WP_Error Array of prepared data, or WP_Error object.
*/
public function prepare_item_for_database( $request ) {
$prepared_post = array();
if ( isset( $request['title'] ) ) {
$prepared_post['post_title'] = sanitize_text_field( $request['title'] );
}
if ( isset( $request['content'] ) ) {
$prepared_post['post_content'] = wp_kses_post( $request['content'] );
}
if ( isset( $request['status'] ) ) {
$prepared_post['post_status'] = sanitize_text_field( $request['status'] );
}
$prepared_post['post_type'] = 'book';
return $prepared_post;
}
/**
* 获取文章 Schema.
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'book',
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( '唯一标识符。', 'my-plugin' ),
'type' => 'integer',
'context' => array( 'view', 'edit', 'embed' ),
'readonly' => true,
),
'title' => array(
'description' => __( '文章标题。', 'my-plugin' ),
'type' => 'string',
'context' => array( 'view', 'edit', 'embed' ),
'arg_options' => array(
'sanitize_callback' => 'sanitize_text_field',
),
),
'content' => array(
'description' => __( '文章内容。', 'my-plugin' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'status' => array(
'description' => __( '文章状态。', 'my-plugin' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'enum' => array( 'publish', 'draft', 'pending', 'private' ),
),
),
);
return $this->add_additional_fields_schema( $schema );
}
/**
* 获取文章链接.
*
* @param WP_Post $post 文章对象.
* @return array
*/
protected function prepare_links( $post ) {
$links = array(
'self' => array(
'href' => rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $post->ID ) ),
),
'collection' => array(
'href' => rest_url( $this->namespace . '/' . $this->rest_base ),
),
);
return $links;
}
/**
* Determine the HTTP status for authorization.
*
* @since 5.5.0
*
* @return int
*/
protected function authorization_status_code() {
if ( is_user_logged_in() ) {
return 403;
}
return 401;
}
}
// 注册控制器
function register_books_routes() {
$controller = new WP_REST_Books_Controller();
$controller->register_routes();
}
add_action( 'rest_api_init', 'register_books_routes' );
代码解释:
WP_REST_Books_Controller
: 继承WP_REST_Controller
,用于处理book
自定义文章类型的 API 请求。$namespace
和$rest_base
: 定义了 API 的命名空间和路由基地址。register_routes()
: 注册了 API 的路由,包括获取文章列表、获取单个文章、创建文章、更新文章和删除文章。get_items_permissions_check()
,get_item_permissions_check()
,create_item_permissions_check()
,update_item_permissions_check()
,delete_item_permissions_check()
: 检查用户权限。get_items()
,get_item()
,create_item()
,update_item()
,delete_item()
: 处理 API 请求,并返回相应的数据。prepare_item_for_response()
: 格式化文章数据,使其符合 API 响应的格式。prepare_item_for_database()
: 准备文章数据以保存到数据库。get_item_schema()
: 定义文章的 Schema。prepare_links()
: 为response添加相关链接。authorization_status_code()
: 根据用户登录状态返回相应的HTTP状态码,用于权限验证失败时返回。register_books_routes()
: 实例化控制器并注册路由。add_action( 'rest_api_init', 'register_books_routes' )
: 在rest_api_init
钩子中注册路由。
5. 实现接口需要注意的点
- 权限验证: 务必实现权限验证逻辑,确保只有授权用户才能访问你的 API 端点。
- 数据验证和清理: 对所有请求参数进行验证和清理,防止恶意数据注入。使用 WordPress 提供的
sanitize_*
函数来清理数据。 - 错误处理: 使用
WP_Error
对象来处理错误,并返回合适的 HTTP 状态码。 - Schema 定义: 尽可能定义清晰的 Schema,这有助于 API 的文档生成和客户端的自动验证。
- 响应格式: 确保 API 响应的格式一致,易于解析。
- 兼容性: 考虑到未来的兼容性,尽量使用 WordPress 提供的 API 函数,避免直接操作数据库。
- 性能: 优化你的 API 代码,减少数据库查询和计算量,提高 API 的响应速度。可以使用缓存来提高性能。
6. 总结
WP_REST_Controller
是构建自定义 WordPress REST API 的基础。 通过继承它,并实现其关键方法,开发者可以创建功能强大,安全可靠的 API 端点。理解其继承体系和接口定义,是高效开发 REST API 的关键。
希望今天的讲解对大家有所帮助!