概述
Schema(模式)是告诉我们应该如何构建其他数据的数据。大多数数据库都实现了某种形式的 schema,使我们能够以更结构化的方式对数据进行推理。WordPress REST API 利用 JSON Schema 来处理其数据的结构化。你可以不使用 schema 来实现端点,但你会错过很多东西。由你自己决定什么最适合你。
JSON Schema
首先,让我们谈谈 JSON 的一些内容。JSON 是一种类似于 JavaScript 对象的易读数据格式。JSON 代表 JavaScript Object Notation(JavaScript 对象表示法)。JSON 的流行度正在急剧增长,似乎正在席卷数据结构的世界。WordPress REST API 使用一种特殊的 JSON 规范,称为 JSON schema。要了解有关 JSON Schema 的更多信息,请查看 JSON Schema 网站 和这个 更容易理解的 JSON Schema 介绍。Schema 为我们提供了许多好处:改进测试、可发现性以及更好的整体结构。让我们看看一个 JSON 数据块。
{
"shouldBeArray": 'LOL definitely not an array',
"shouldBeInteger": ['lolz', 'you', 'need', 'schema'],
"shouldBeString": 123456789
}JSON 解析器可以毫无问题地处理这些数据,而不会抱怨任何内容,因为它是有效的 JSON。客户端和服务器对数据一无所知,也不知道期望什么,它们只是看到 JSON。通过实现 schema,我们实际上可以简化代码库。Schema 将帮助更好地结构化我们的数据,以便我们的应用程序可以更轻松地推理我们与 WordPress REST API 的交互。WordPress REST API 并不强制你使用 schema,但鼓励这样做。有两种方式可以将 schema 数据纳入 API;资源的 schema 和我们注册的参数的 schema。
资源 Schema
资源的 schema 指示特定对象存在的字段。当我们注册路由时,我们还可以指定该路由的资源 schema。让我们看看一个简单的评论 schema 在 JSON schema 的 PHP 表示中可能是什么样子。
// Register our routes.
function prefix_register_my_comment_route() {
register_rest_route( 'my-namespace/v1', '/comments', array(
// Notice how we are registering multiple endpoints the 'schema' equates to an OPTIONS request.
array(
'methods' => 'GET',
'callback' => 'prefix_get_comment_sample',
),
// Register our schema callback.
'schema' => 'prefix_get_comment_schema',
) );
}
add_action( 'rest_api_init', 'prefix_register_my_comment_route' );
/**
* Grabs the five most recent comments and outputs them as a rest response.
*
* @param WP_REST_Request $request Current request.
*/
function prefix_get_comment_sample( $request ) {
$args = array(
'post_per_page' => 5,
);
$comments = get_comments( $args );
$data = array();
if ( empty( $comments ) ) {
return rest_ensure_response( $data );
}
foreach ( $comments as $comment ) {
$response = prefix_rest_prepare_comment( $comment, $request );
$data[] = prefix_prepare_for_collection( $response );
}
// Return all of our comment response data.
return rest_ensure_response( $data );
}
/**
* Matches the comment data to the schema we want.
*
* @param WP_Comment $comment The comment object whose response is being prepared.
*/
function prefix_rest_prepare_comment( $comment, $request ) {
$comment_data = array();
$schema = prefix_get_comment_schema( $request );
// We are also renaming the fields to more understandable names.
if ( isset( $schema['properties']['id'] ) ) {
$comment_data['id'] = (int) $comment->comment_id;
}
if ( isset( $schema['properties']['author'] ) ) {
$comment_data['author'] = (int) $comment->user_id;
}
if ( isset( $schema['properties']['content'] ) ) {
$comment_data['content'] = apply_filters( 'comment_text', $comment->comment_content, $comment );
}
return rest_ensure_response( $comment_data );
}
/**
* Prepare a response for inserting into a collection of responses.
*
* This is copied from WP_REST_Controller class in the WP REST API v2 plugin.
*
* @param WP_REST_Response $response Response object.
* @return array Response data, ready for insertion into collection data.
*/
function prefix_prepare_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( array( $server, 'get_compact_response_links' ), $response );
} else {
$links = call_user_func( array( $server, 'get_response_links' ), $response );
}
if ( ! empty( $links ) ) {
$data['_links'] = $links;
}
return $data;
}
/**
* Get our sample schema for comments.
*
* @param WP_REST_Request $request Current request.
*/
function prefix_get_comment_schema( $request ) {
$schema = array(
// This tells the spec of JSON Schema we are using which is draft 4.
'$schema' => 'http://json-schema.org/draft-04/schema#',
// The title property marks the identity of the resource.
'title' => 'comment',
'type' => 'object',
// In JSON Schema you can specify object properties in the properties attribute.
'properties' => array(
'id' => array(
'description' => esc_html__( 'Unique identifier for the object.', 'my-textdomain' ),
'type' => 'integer',
'context' => array( 'view', 'edit', 'embed' ),
'readonly' => true,
),
'author' => array(
'description' => esc_html__( 'The id of the user object, if author was a user.', 'my-textdomain' ),
'type' => 'integer',
),
'content' => array(
'description' => esc_html__( 'The content for the object.', 'my-textdomain' ),
'type' => 'string',
),
),
);
return $schema;
}如果你注意到,每个评论资源现在都匹配我们指定的 schema。我们在 prefix_rest_prepare_comment() 中进行了此切换。通过为资源创建 schema,我们现在可以通过发出 OPTIONS 请求来查看该 schema。这有什么用?如果我们希望其他语言(例如 JavaScript)解释我们的数据并验证来自我们端点的数据,JavaScript 需要知道我们的数据结构是怎样的。当我们提供 schema 时,我们为其他作者和我们自己打开了大门,以便我们可以以一致的方式构建在端点上。
Schema 提供了机器可读的数据,因此任何可以读取 JSON 的东西都可以了解它正在查看哪种类型的数据。当我们通过向 https://ourawesomesite.com/wp-json/ 发出 GET 请求来查看 API 索引时,我们会返回我们 API 的 schema,使其他人能够编写客户端库来解释我们的数据。读取 schema 数据的这个过程称为发现(discovery)。当我们为资源提供 schema 时,我们通过向该路由发出 OPTIONS 请求使该资源可被发现。暴露资源 schema 只是我们的 schema 拼图的一部分。我们还希望为我们的注册参数使用 schema。
参数 Schema
当我们为端点注册请求参数时,我们还可以使用 JSON Schema 为我们提供有关参数应该是什么的数据。这使我们能够编写可重用的验证库,以便随着端点的扩展而重用。Schema 前期工作更多,但如果你要编写一个将增长的生产应用程序,你绝对应该考虑使用 schema。让我们看看使用参数 schema 和验证的示例。
// 注册我们的路由。
function prefix_register_my_arg_route() {
register_rest_route( 'my-namespace/v1', '/schema-arg', array(
// 在这里我们注册我们的端点。
array(
'methods' => 'GET',
'callback' => 'prefix_get_item',
'args' => prefix_get_endpoint_args(),
),
) );
}
// 将注册挂钩到 'rest_api_init' 钩子。
add_action( 'rest_api_init', 'prefix_register_my_arg_route' );
/**
* 返回请求参数 `my-arg` 作为 rest 响应。
*
* @param WP_REST_Request $request 当前请求。
*/
function prefix_get_item( $request ) {
// 如果我们在 schema 中没有使用 required,当 my arg 未设置时会抛出错误。
return rest_ensure_response( $request['my-arg'] );
}
/**
* 获取此示例端点的参数 schema。
*/
function prefix_get_endpoint_args() {
$args = array();
// 在这里我们添加 JSON Schema 的 PHP 表示形式。
$args['my-arg'] = array(
'description' => esc_html__( '这是我们的端点返回的参数。', 'my-textdomain' ),
'type' => 'string',
'validate_callback' => 'prefix_validate_my_arg',
'sanitize_callback' => 'prefix_sanitize_my_arg',
'required' => true,
);
return $args;
}
/**
* 我们的 `my-arg` 参数的验证回调。
*
* @param mixed $value my-arg 参数的值。
* @param WP_REST_Request $request 当前请求对象。
* @param string $param 此情况下参数的名称,即 'my-arg'。
*/
function prefix_validate_my_arg( $value, $request, $param ) {
$attributes = $request->get_attributes();
if ( isset( $attributes['args'][ $param ] ) ) {
$argument = $attributes['args'][ $param ];
// 检查以确保我们的参数是字符串。
if ( 'string' === $argument['type'] && ! is_string( $value ) ) {
return new WP_Error( 'rest_invalid_param', sprintf( esc_html__( '%1$s 不是类型 %2$s', 'my-textdomain' ), $param, 'string' ), array( 'status' => 400 ) );
}
} else {
// 由于我们将此参数指定为 required,此代码不会执行。
// 如果重用此验证回调且没有 required args,则会触发。
return new WP_Error( 'rest_invalid_param', sprintf( esc_html__( '%s 未注册为请求参数。', 'my-textdomain' ), $param ), array( 'status' => 400 ) );
}
// 如果到达这里,则数据有效。
return true;
}
/**
* 我们的 `my-arg` 参数的清理回调。
*
* @param mixed $value my-arg 参数的值。
* @param WP_REST_Request $request 当前请求对象。
* @param string $param 此情况下参数的名称,即 'my-arg'。
*/
function prefix_sanitize_my_arg( $value, $request, $param ) {
$attributes = $request->get_attributes();
if ( isset( $attributes['args'][ $param ] ) ) {
$argument = $attributes['args'][ $param ];
// 检查以确保我们的参数是字符串。
if ( 'string' === $argument['type'] ) {
return sanitize_text_field( $value );
}
} else {
// 由于我们将此参数指定为 required,此代码不会执行。
// 如果重用此验证回调且没有 required args,则会触发。
return new WP_Error( 'rest_invalid_param', sprintf( esc_html__( '%s 未注册为请求参数。', 'my-textdomain' ), $param ), array( 'status' => 400 ) );
}
// 如果到达这里,则出错了,不要使用用户输入。
return new WP_Error( 'rest_api_sad', esc_html__( '出了严重的错误。', 'my-textdomain' ), array( 'status' => 500 ) );
}在上面的示例中,我们抽象掉了使用
'my-arg' 名称。我们可以为任何应为我们指定了 schema 的字符串参数使用这些验证和清理函数。随着你的代码库和端点的增长,schema 将有助于保持你的代码轻量且易于维护。没有 schema,你可以进行验证和清理,但跟踪哪些函数应该验证什么会更加困难。通过向请求参数添加 schema,我们还可以向客户端暴露我们的参数 schema,以便可以构建客户端侧的验证库,这可以通过防止无效请求发送到 API 来帮助提高性能。如果你不习惯使用 schema,仍然可以为每个参数拥有 validate/sanitize 回调,在某些情况下进行自定义验证可能是最有意义的。
概述
Schema 在某些时候可能看起来愚蠢,并且可能像不必要的工作,但如果你想要可维护、可发现且易于扩展的端点,使用 schema 是必不可少的。Schema 还有助于自我记录你的端点,既对人类也对计算机!