概要
スキーマは、他のデータがどのように構造化されるべきかを示すデータです。ほとんどのデータベースは、データをより構造的に考えることを可能にする何らかの形式のスキーマを実装しています。WordPress REST APIは、データの構造化を処理するためにJSONスキーマを利用しています。スキーマを使用せずにエンドポイントを実装することもできますが、多くのことを見逃すことになります。何が最適かはあなた次第です。
JSONスキーマ
まず、JSONについて少し話しましょう。JSONは、人間が読みやすいデータ形式で、JavaScriptオブジェクトに似ています。JSONはJavaScript Object Notationの略です。JSONは急速に人気が高まっており、データ構造の世界を席巻しているようです。WordPress REST APIは、JSONスキーマとして知られる特別な仕様を使用しています。JSON Schemaについて詳しく知りたい場合は、JSON Schemaウェブサイトや、このより理解しやすいJSON Schemaの紹介をチェックしてください。スキーマは多くの利点を提供します:テストの改善、発見性、全体的な構造の向上です。JSONデータの塊を見てみましょう。
{
"shouldBeArray": 'LOL definitely not an array',
"shouldBeInteger": ['lolz', 'you', 'need', 'schema'],
"shouldBeString": 123456789
}
JSONパーサーは、そのデータを問題なく処理し、何も文句を言いません。なぜなら、それは有効なJSONだからです。クライアントとサーバーはデータについて何も知らず、何を期待するかもわかりません。ただJSONを見るだけです。スキーマを実装することで、実際にコードベースを簡素化できます。スキーマはデータをより良く構造化するのに役立ち、アプリケーションがWordPress REST APIとの相互作用をより簡単に考えることができるようになります。WordPress REST APIはスキーマの使用を強制しませんが、推奨されています。スキーマデータはAPIに組み込まれる方法が2つあります;リソースのスキーマと登録された引数のスキーマです。
リソーススキーマ
リソースのスキーマは、特定のオブジェクトにどのフィールドが存在するかを示します。ルートを登録する際に、ルートのリソーススキーマも指定できます。JSONスキーマの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;
}
各コメントリソースが、指定したスキーマに一致していることに気づくでしょう。この変更はprefix_rest_prepare_comment()
で行いました。リソースのスキーマを作成することで、OPTIONS
リクエストを行うことでこのスキーマを表示できるようになりました。これはなぜ便利なのでしょうか?他の言語、例えばJavaScriptが私たちのデータを解釈し、エンドポイントからのデータを検証するためには、JavaScriptが私たちのデータがどのように構造化されているかを知る必要があります。スキーマを提供することで、他の著者や私たち自身が、一貫した方法でエンドポイントの上に構築するための扉を開きます。
スキーマは機械可読データを提供するため、JSONを読み取ることができるものは何でも、どのようなデータを見ているのかを理解できます。APIインデックスをGET
リクエストをhttps://ourawesomesite.com/wp-json/
に送信することで確認すると、APIのスキーマが返され、他の人が私たちのデータを解釈するためのクライアントライブラリを書くことができるようになります。このスキーマデータを読み取るプロセスは、ディスカバリーとして知られています。リソースのためにスキーマを提供すると、そのリソースをOPTIONS
リクエストを通じて発見可能にします。リソーススキーマを公開することは、スキーマパズルの一部に過ぎません。登録された引数のためにもスキーマを使用したいと考えています。
引数スキーマ
エンドポイントのリクエスト引数を登録する際に、JSONスキーマを使用して引数がどのようなものであるべきかのデータを提供することもできます。これにより、エンドポイントが拡張されるにつれて再利用可能な検証ライブラリを書くことが可能になります。スキーマは最初は手間がかかりますが、成長するプロダクションアプリケーションを書くつもりなら、スキーマの使用を検討すべきです。引数スキーマと検証の使用例を見てみましょう。
// Register our routes.
function prefix_register_my_arg_route() {
register_rest_route( 'my-namespace/v1', '/schema-arg', array(
// Here we register our endpoint.
array(
'methods' => 'GET',
'callback' => 'prefix_get_item',
'args' => prefix_get_endpoint_args(),
),
) );
}
// Hook registration into 'rest_api_init' hook.
add_action( 'rest_api_init', 'prefix_register_my_arg_route' );
/**
* Returns the request argument `my-arg` as a rest response.
*
* @param WP_REST_Request $request Current request.
*/
function prefix_get_item( $request ) {
// If we didn't use required in the schema this would throw an error when my arg is not set.
return rest_ensure_response( $request['my-arg'] );
}
/**
* Get the argument schema for this example endpoint.
*/
function prefix_get_endpoint_args() {
$args = array();
// Here we add our PHP representation of JSON Schema.
$args['my-arg'] = array(
'description' => esc_html__( 'This is the argument our endpoint returns.', 'my-textdomain' ),
'type' => 'string',
'validate_callback' => 'prefix_validate_my_arg',
'sanitize_callback' => 'prefix_sanitize_my_arg',
'required' => true,
);
return $args;
}
/**
* Our validation callback for `my-arg` parameter.
*
* @param mixed $value Value of the my-arg parameter.
* @param WP_REST_Request $request Current request object.
* @param string $param The name of the parameter in this case, 'my-arg'.
*/
function prefix_validate_my_arg( $value, $request, $param ) {
$attributes = $request->get_attributes();
if ( isset( $attributes['args'][ $param ] ) ) {
$argument = $attributes['args'][ $param ];
// Check to make sure our argument is a string.
if ( 'string' === $argument['type'] && ! is_string( $value ) ) {
return new WP_Error( 'rest_invalid_param', sprintf( esc_html__( '%1$s is not of type %2$s', 'my-textdomain' ), $param, 'string' ), array( 'status' => 400 ) );
}
} else {
// This code won't execute because we have specified this argument as required.
// If we reused this validation callback and did not have required args then this would fire.
return new WP_Error( 'rest_invalid_param', sprintf( esc_html__( '%s was not registered as a request argument.', 'my-textdomain' ), $param ), array( 'status' => 400 ) );
}
// If we got this far then the data is valid.
return true;
}
/**
* Our santization callback for `my-arg` parameter.
*
* @param mixed $value Value of the my-arg parameter.
* @param WP_REST_Request $request Current request object.
* @param string $param The name of the parameter in this case, 'my-arg'.
*/
function prefix_sanitize_my_arg( $value, $request, $param ) {
$attributes = $request->get_attributes();
if ( isset( $attributes['args'][ $param ] ) ) {
$argument = $attributes['args'][ $param ];
// Check to make sure our argument is a string.
if ( 'string' === $argument['type'] ) {
return sanitize_text_field( $value );
}
} else {
// This code won't execute because we have specified this argument as required.
// If we reused this validation callback and did not have required args then this would fire.
return new WP_Error( 'rest_invalid_param', sprintf( esc_html__( '%s was not registered as a request argument.', 'my-textdomain' ), $param ), array( 'status' => 400 ) );
}
// If we got this far then something went wrong don't use user input.
return new WP_Error( 'rest_api_sad', esc_html__( 'Something went terribly wrong.', 'my-textdomain' ), array( 'status' => 500 ) );
}
上記の例では、'my-arg'
という名前の使用を抽象化しました。指定したスキーマのために文字列であるべき他の引数に対して、これらの検証およびサニタイズ関数を使用できます。コードベースとエンドポイントが成長するにつれて、スキーマはコードを軽量で保守可能に保つのに役立ちます。スキーマがない場合でも、検証とサニタイズは可能ですが、どの関数が何を検証すべきかを追跡するのが難しくなります。リクエスト引数にスキーマを追加することで、引数スキーマをクライアントに公開できるため、クライアント側で検証ライブラリを構築でき、無効なリクエストがAPIに送信されるのを防ぐことでパフォーマンスを向上させることができます。
スキーマの使用に不安がある場合でも、各引数に対して検証/サニタイズコールバックを持つことは可能であり、場合によってはカスタム検証を行うのが最も理にかなっていることもあります。
概要
スキーマは時には馬鹿げているように見え、不要な作業のように思えるかもしれませんが、保守可能で発見可能で簡単に拡張可能なエンドポイントを望むのであれば、スキーマを使用することは不可欠です。スキーマはまた、人間とコンピュータの両方に対してエンドポイントを自己文書化するのに役立ちます!