概要
スキーマとは、他のデータがどのように構造化されるべきかを示すデータです。ほとんどのデータベースは、より構造化されたやり方でデータについて説明することを可能にする、何らかの形のスキーマを実装しています。WordPress REST API は、JSON スキーマを使用してそのデータの構造化を処理します。スキーマを使わずにエンドポイントを実装できますが、多くのものを失うことになります。何が一番自分に合っているかは、あなた次第です。
JSON スキーマ
まず、JSON について少し説明しましょう。JSON は、JavaScript のオブジェクトに似た、人間が読めるデータフォーマットです。JSON は JavaScript Object Notation (JavaScript オブジェクト記法) の略です。JSON の人気は急上昇しており、データ構造の世界を席巻しているように見えます。WordPress REST API は、JSON スキーマとして知られる JSON の特別な仕様を使用しています。JSON スキーマについてもっと知りたい人は、JSONスキーマのウェブサイトとこちらのJSONスキーマについての理解しやすい導入をご覧ください。スキーマには多くの利点があります: テストの改善、発見しやすさ、全体的な構造の改善などです。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 を読むことができるものであれば、どんなデータを見ているのかを理解できる可能性があります。https://ourawesomesite.com/wp-json/
に GET
リクエストすることで API インデックスを参照する際、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 に送信されるのを防ぎ、パフォーマンスの向上が可能になります。
概要
スキーマは、一見バカバカしく思えることもあるかもしれませんし、不必要な作業のように思えるるかもしれませんが、メンテナンスしやすく、みつけやすく、簡単に拡張できるエンドポイントを望むのであれば、スキーマを使うことは不可欠です。スキーマはまた、人間にとってもコンピューターにとっても、エンドポイントの自己ドキュメント化に役立ちます !