各位听众,晚上好!今天咱们来聊聊 WordPress REST API 的骨架——WP_REST_Controller
类。这玩意儿就像盖楼的地基,你想在 WordPress 里搭建自己的 REST API 大厦,就得先搞清楚这地基怎么打。
一、 为什么需要 WP_REST_Controller
?
想象一下,如果没有一个统一的标准,每个人都按照自己的方式来创建 API 端点,那场面简直混乱不堪。你可能需要花大量时间去理解每个插件或主题的 API 使用方式,调试起来更是噩梦。
WP_REST_Controller
的出现就是为了解决这个问题。它提供了一个标准的框架,帮你规范化地创建 REST API 端点。就像一个模版,让你按照固定的格式去填充内容,从而保证 API 的一致性、可维护性和可扩展性。
二、 WP_REST_Controller
的核心概念
WP_REST_Controller
本身是一个抽象类,你不能直接实例化它。你需要创建一个新的类,继承它,并实现其中的一些方法。
简单来说,WP_REST_Controller
帮你完成了以下几件事:
- 注册路由: 告诉你应该把 API 端点注册到哪里,用什么 URL 访问。
- 请求方法处理: 帮你区分
GET
、POST
、PUT
、DELETE
等不同的请求方法,并分配给相应的处理函数。 - 权限验证: 帮你检查用户是否有权限访问该 API 端点。
- 数据格式化: 帮你将数据转换为标准的 JSON 格式,方便客户端解析。
三、 动手实践:创建一个自定义的 REST API 端点
咱们现在就来一步步创建一个简单的 REST API 端点,这个端点可以获取和修改自定义的文章类型 "book" 的信息。
1. 定义自定义文章类型 (如果还没做)
首先,确保你已经定义了一个名为 "book" 的自定义文章类型。如果没有,可以在你的主题的 functions.php
文件或插件中添加以下代码:
add_action( 'init', 'create_book_post_type' );
function create_book_post_type() {
register_post_type( 'book',
array(
'labels' => array(
'name' => __( 'Books' ),
'singular_name' => __( 'Book' )
),
'public' => true,
'has_archive' => true,
'supports' => array( 'title', 'editor', 'custom-fields' ), // 支持的字段
'show_in_rest' => true, // 关键:让它在 REST API 中可用
)
);
}
show_in_rest => true
是关键,它会告诉 WordPress 将这个文章类型暴露在 REST API 中。不过,我们这里要创建的是自定义的端点,所以这个其实不是必须的。
2. 创建控制器类
创建一个新的 PHP 文件,例如 class-wp-rest-book-controller.php
,并添加以下代码:
<?php
/**
* REST controller for Books.
*/
class WP_REST_Book_Controller extends WP_REST_Controller {
/**
* The base of this route.
*
* @var string
*/
protected $base = 'books'; // API 端点的基本路径,例如 /wp-json/myplugin/v1/books
/**
* Registers the routes for the objects of the controller.
*
* @since 4.7.0
*
* @see register_rest_route()
*/
public function register_routes() {
$namespace = 'myplugin/v1'; // 命名空间
register_rest_route( $namespace, '/' . $this->base, array(
array(
'methods' => WP_REST_Server::READABLE, // GET
'callback' => array( $this, 'get_items' ), // 获取所有书籍
'permission_callback' => array( $this, 'get_items_permissions_check' ), // 权限验证
'args' => array(),
),
array(
'methods' => WP_REST_Server::CREATABLE, // POST
'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( $namespace, '/' . $this->base . '/(?P<id>[d]+)', array(
array(
'methods' => WP_REST_Server::READABLE, // GET
'callback' => array( $this, 'get_item' ), // 获取单本书籍
'permission_callback' => array( $this, 'get_item_permissions_check' ), // 权限验证
'args' => array(
'id' => array(
'validate_callback' => function( $param, $request, $key ) {
return is_numeric( $param ); // ID 必须是数字
}
),
),
),
array(
'methods' => WP_REST_Server::EDITABLE, // PUT
'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, // DELETE
'callback' => array( $this, 'delete_item' ), // 删除书籍
'permission_callback' => array( $this, 'delete_item_permissions_check' ), // 权限验证
'args' => array(
'force' => array(
'default' => false,
'type' => 'boolean',
'description' => __( 'Whether to bypass Trash and force deletion.' ),
),
),
),
) );
}
/**
* Checks if a given request has access to get items.
*
* @param WP_REST_Request $request Full data about the request.
* @return WP_Error|bool
*/
public function get_items_permissions_check( $request ) {
// 只有管理员才能查看所有书籍
if ( ! current_user_can( 'manage_options' ) ) {
return new WP_Error( 'rest_forbidden', __( 'You cannot view the resource.' ), array( 'status' => $this->authorization_status_code() ) );
}
return true;
}
/**
* Retrieves all items.
*
* @param WP_REST_Request $request Full data about the request.
* @return WP_Error|WP_REST_Response
*/
public function get_items( $request ) {
$posts = get_posts( array(
'post_type' => 'book',
'posts_per_page' => -1, // 获取所有书籍
) );
$data = array();
foreach ( $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 );
}
/**
* Checks if a given request has access to get a specific item.
*
* @param WP_REST_Request $request Full data about the request.
* @return WP_Error|bool
*/
public function get_item_permissions_check( $request ) {
return $this->get_items_permissions_check( $request ); // 使用相同的权限检查
}
/**
* Retrieves one item from the collection.
*
* @param WP_REST_Request $request Full data about the request.
* @return WP_Error|WP_REST_Response
*/
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_post_invalid_id', __( 'Invalid post ID.' ), array( 'status' => 404 ) );
}
$data = $this->prepare_item_for_response( $post, $request );
return rest_ensure_response( $data );
}
/**
* Checks if a given request has access to create items.
*
* @param WP_REST_Request $request Full data about the request.
* @return WP_Error|bool
*/
public function create_item_permissions_check( $request ) {
if ( ! current_user_can( 'publish_posts' ) ) { // 只有能发布文章的用户才能创建书籍
return new WP_Error( 'rest_forbidden', __( 'You cannot create the resource.' ), array( 'status' => $this->authorization_status_code() ) );
}
return true;
}
/**
* Creates one item from the collection.
*
* @param WP_REST_Request $request Full data about the request.
* @return WP_Error|WP_REST_Response
*/
public function create_item( $request ) {
$title = sanitize_text_field( $request['title'] );
$content = wp_kses_post( $request['content'] );
$post_id = wp_insert_post( array(
'post_type' => 'book',
'post_title' => $title,
'post_content' => $content,
'post_status' => 'publish',
) );
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 );
}
/**
* Checks if a given request has access to update a specific item.
*
* @param WP_REST_Request $request Full data about the request.
* @return WP_Error|bool
*/
public function update_item_permissions_check( $request ) {
$post = get_post( (int) $request['id'] );
if ( ! $post || $post->post_type !== 'book' ) {
return new WP_Error( 'rest_post_invalid_id', __( 'Invalid post ID.' ), array( 'status' => 404 ) );
}
if ( ! current_user_can( 'edit_post', (int) $request['id'] ) ) { // 只有能编辑该文章的用户才能更新
return new WP_Error( 'rest_forbidden', __( 'You cannot update the resource.' ), array( 'status' => $this->authorization_status_code() ) );
}
return true;
}
/**
* Updates one item from the collection.
*
* @param WP_REST_Request $request Full data about the request.
* @return WP_Error|WP_REST_Response
*/
public function update_item( $request ) {
$id = (int) $request['id'];
$title = sanitize_text_field( $request['title'] );
$content = wp_kses_post( $request['content'] );
$updated = wp_update_post( array(
'ID' => $id,
'post_title' => $title,
'post_content' => $content,
) );
if ( is_wp_error( $updated ) ) {
return $updated;
}
$post = get_post( $id );
$data = $this->prepare_item_for_response( $post, $request );
return rest_ensure_response( $data );
}
/**
* Checks if a given request has access to delete a specific item.
*
* @param WP_REST_Request $request Full data about the request.
* @return WP_Error|bool
*/
public function delete_item_permissions_check( $request ) {
$post = get_post( (int) $request['id'] );
if ( ! $post || $post->post_type !== 'book' ) {
return new WP_Error( 'rest_post_invalid_id', __( 'Invalid post ID.' ), array( 'status' => 404 ) );
}
if ( ! current_user_can( 'delete_post', (int) $request['id'] ) ) { // 只有能删除该文章的用户才能删除
return new WP_Error( 'rest_forbidden', __( 'You cannot delete the resource.' ), array( 'status' => $this->authorization_status_code() ) );
}
return true;
}
/**
* Deletes one item from the collection.
*
* @param WP_REST_Request $request Full data about the request.
* @return WP_Error|WP_REST_Response
*/
public function delete_item( $request ) {
$id = (int) $request['id'];
$force = isset( $request['force'] ) ? (bool) $request['force'] : false;
$deleted = wp_delete_post( $id, $force );
if ( ! $deleted ) {
return new WP_Error( 'rest_cannot_delete', __( 'The post cannot be deleted.' ), array( 'status' => 500 ) );
}
$data = array(
'deleted' => true,
'previous' => $this->prepare_item_for_response( $deleted, $request ),
);
return rest_ensure_response( $data );
}
/**
* Prepares the item for the REST response.
*
* @param mixed $item WordPress representation of the item.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response|WP_Error Response object.
*/
public function prepare_item_for_response( $item, $request ) {
$data = array(
'id' => $item->ID,
'title' => $item->post_title,
'content' => $item->post_content,
'link' => get_permalink( $item->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_link( 'self', rest_url( sprintf( '%s/%s/%d', 'myplugin/v1', $this->base, $item->ID ) ) );
return $response;
}
/**
* Retrieves the item's schema, defining the shape of the data.
*
* @return array Public-facing schema for the endpoint.
*/
public function get_item_schema() {
if ( $this->schema ) {
return $this->add_additional_fields_schema( $this->schema );
}
$schema = array(
// This tells the spec of JSON Schema we are using.
'$schema' => 'http://json-schema.org/draft-04/schema#',
// The title property marks the identity of the resource.
'title' => 'book',
'type' => 'object',
// In JSON Schema you can specify object properties in the "properties" attribute.
'properties' => array(
'id' => array(
'description' => __( 'Unique identifier for the object.' ),
'type' => 'integer',
'context' => array( 'view', 'edit', 'embed' ),
'readonly' => true,
),
'title' => array(
'description' => __( 'The title for the object.' ),
'type' => 'string',
'context' => array( 'view', 'edit', 'embed' ),
'arg_options' => array(
'sanitize_callback' => 'sanitize_text_field',
),
'required' => true,
),
'content' => array(
'description' => __( 'The content for the object.' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'wp_kses_post',
),
),
),
);
$this->schema = $schema;
return $this->add_additional_fields_schema( $this->schema );
}
/**
* Retrieves the query params for the objects collection.
*
* @return array Collection parameters.
*/
public function get_collection_params() {
return array(
'context' => $this->get_context_param(),
'page' => array(
'description' => __( 'Current page of the collection.' ),
'type' => 'integer',
'default' => 1,
'sanitize_callback' => 'absint',
'validate_callback' => 'rest_validate_request_arg',
'minimum' => 1,
),
'per_page' => array(
'description' => __( 'Maximum number of items to be returned in result set.' ),
'type' => 'integer',
'default' => 10,
'sanitize_callback' => 'absint',
'validate_callback' => 'rest_validate_request_arg',
'maximum' => 100,
),
'search' => array(
'description' => __( 'Limit results to those matching a string.' ),
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
'validate_callback' => 'rest_validate_request_arg',
),
);
}
/**
* Retrieves the authorization callback's status code.
*
* @since 4.7.0
*
* @return int Status code.
*/
protected function authorization_status_code() {
if ( is_user_logged_in() ) {
return 403;
}
return 401;
}
}
3. 注册路由
在你的主题的 functions.php
文件或插件中,引入并实例化你的控制器类,并注册路由:
add_action( 'rest_api_init', 'register_book_routes' );
function register_book_routes() {
require_once 'class-wp-rest-book-controller.php'; // 引入控制器类文件
$controller = new WP_REST_Book_Controller();
$controller->register_routes();
}
4. 代码详解
现在,咱们来详细分析一下上面的代码:
-
class WP_REST_Book_Controller extends WP_REST_Controller
: 定义了一个名为WP_REST_Book_Controller
的类,它继承了WP_REST_Controller
。 -
$base = 'books';
: 定义了 API 端点的基本路径。 最终API的路径会是/wp-json/myplugin/v1/books
。 -
register_routes()
: 这个方法是核心,它负责注册 API 端点。register_rest_route( $namespace, '/' . $this->base, ... )
: 注册一个路由,第一个参数是命名空间,第二个参数是路径,第三个参数是一个数组,包含了不同请求方法的处理函数。WP_REST_Server::READABLE
、WP_REST_Server::CREATABLE
、WP_REST_Server::EDITABLE
、WP_REST_Server::DELETABLE
: 分别代表GET
、POST
、PUT
、DELETE
请求方法。callback
: 指定处理该请求的函数。permission_callback
: 指定权限验证函数。args
: 指定请求参数的验证规则。
-
get_items_permissions_check()
、get_item_permissions_check()
、create_item_permissions_check()
、update_item_permissions_check()
、delete_item_permissions_check()
: 这些方法负责权限验证。 它们接受一个$request
对象作为参数,其中包含了请求的所有信息。 如果用户有权限,则返回true
,否则返回一个WP_Error
对象。 -
get_items()
、get_item()
、create_item()
、update_item()
、delete_item()
: 这些方法是实际的处理函数。get_items()
: 获取所有书籍。get_item()
: 获取单本书籍。create_item()
: 创建书籍。update_item()
: 更新书籍。delete_item()
: 删除书籍。- 这些方法都接受一个
$request
对象作为参数,并返回一个WP_REST_Response
对象或一个WP_Error
对象。
-
prepare_item_for_response()
: 这个方法负责将数据格式化为标准的 JSON 格式。 它接受一个$item
对象和一个$request
对象作为参数,并返回一个数组。 -
get_item_schema()
: 这个方法定义了 API 端点返回的数据的结构。 它返回一个数组,包含了每个字段的描述、类型和上下文。
5. 测试 API 端点
现在,你可以使用任何 REST API 客户端(例如 Postman)来测试你的 API 端点。
- 获取所有书籍:
GET /wp-json/myplugin/v1/books
- 获取单本书籍:
GET /wp-json/myplugin/v1/books/{id}
(将{id}
替换为实际的书籍 ID) - 创建书籍:
POST /wp-json/myplugin/v1/books
(需要在请求体中传递title
和content
参数) - 更新书籍:
PUT /wp-json/myplugin/v1/books/{id}
(将{id}
替换为实际的书籍 ID,并在请求体中传递要更新的title
和content
参数) - 删除书籍:
DELETE /wp-json/myplugin/v1/books/{id}
(将{id}
替换为实际的书籍 ID,可以传递force=true
参数来强制删除)
四、 重点方法解析
咱们来重点看看几个关键方法:
1. register_routes()
这个方法是灵魂,它定义了你的 API 端点的 URL 结构和对应的处理函数。
方法 | 描述 |
---|---|
GET |
从服务器获取数据。例如,获取所有书籍或单本书籍。 |
POST |
向服务器提交数据,通常用于创建新的资源。例如,创建一个新的书籍。 |
PUT |
更新服务器上的资源。例如,更新一本书籍的标题或内容。 |
DELETE |
从服务器删除资源。例如,删除一本书籍。 |
callback |
指定处理该请求的函数。 |
permission_callback |
指定权限验证函数。 |
args |
定义请求参数的结构,包括参数的类型、描述、是否必填、验证规则等。 WordPress REST API 会自动根据这里的定义来验证请求参数,并返回错误信息。 |
2. 权限验证方法
例如 get_items_permissions_check()
,这些方法至关重要,它们决定了谁可以访问你的 API 端点。
public function get_items_permissions_check( $request ) {
// 只有管理员才能查看所有书籍
if ( ! current_user_can( 'manage_options' ) ) {
return new WP_Error( 'rest_forbidden', __( 'You cannot view the resource.' ), array( 'status' => $this->authorization_status_code() ) );
}
return true;
}
current_user_can()
是 WordPress 内置的权限检查函数,可以根据用户角色或权限来判断用户是否可以执行某个操作。
3. 数据处理方法
例如 get_items()
、create_item()
、update_item()
,这些方法负责实际的数据操作。
public function get_items( $request ) {
$posts = get_posts( array(
'post_type' => 'book',
'posts_per_page' => -1, // 获取所有书籍
) );
$data = array();
foreach ( $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 );
}
get_posts()
: WordPress 内置的函数,用于获取文章。prepare_item_for_response()
: 将文章数据格式化为标准的 JSON 格式。rest_ensure_response()
: 确保返回的是一个WP_REST_Response
对象。
4. prepare_item_for_response()
这个方法是数据格式化的关键。它将 WordPress 的数据结构(例如 WP_Post
对象)转换为 API 响应所需的 JSON 格式。
public function prepare_item_for_response( $item, $request ) {
$data = array(
'id' => $item->ID,
'title' => $item->post_title,
'content' => $item->post_content,
'link' => get_permalink( $item->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_link( 'self', rest_url( sprintf( '%s/%s/%d', 'myplugin/v1', $this->base, $item->ID ) ) );
return $response;
}
5. get_item_schema()
这个方法定义了 API 端点返回的数据的结构。
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'book',
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Unique identifier for the object.' ),
'type' => 'integer',
'context' => array( 'view', 'edit', 'embed' ),
'readonly' => true,
),
'title' => array(
'description' => __( 'The title for the object.' ),
'type' => 'string',
'context' => array( 'view', 'edit', 'embed' ),
'arg_options' => array(
'sanitize_callback' => 'sanitize_text_field',
),
'required' => true,
),
'content' => array(
'description' => __( 'The content for the object.' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'wp_kses_post',
),
),
),
);
return $schema;
}
get_item_schema
的作用:
- 文档化: 提供 API 的文档,让开发者知道 API 端点返回的数据是什么样的。
- 数据验证: 用于验证客户端提交的数据是否符合规范。
- 自动生成 API 文档: 一些工具可以根据 schema 自动生成 API 文档。
五、 总结
WP_REST_Controller
提供了一个强大的框架,可以帮助你规范化地创建 WordPress REST API 端点。 记住,关键在于:
- 继承
WP_REST_Controller
- 定义
$base
- 实现
register_routes()
- 实现权限验证方法
- 实现数据处理方法
- 实现
prepare_item_for_response()
- 实现
get_item_schema()
当然,这只是一个简单的例子。 实际应用中,你可能需要处理更复杂的数据结构、验证更复杂的参数,并实现更高级的权限控制。
希望今天的讲座能帮助你更好地理解 WP_REST_Controller
,并开始构建你自己的 WordPress REST API 大厦! 祝大家编程愉快!