WordPressでREST APIを使う前編

API Fetch について
WordPressのブロック開発メモ その11 REST API へのアクセス

基本的な REST API
(σ・ω・)σ WordPressでREST APIを使う前編
WordPressでREST APIを使う後編 REST APIの登録と REST コントローラ

REST API コントローラの実装
WP_REST_Controller メソッド前編
WP_REST_Controller メソッド後編 追加フィールド
WordPress REST API Controllerを実装してみる。

WordPressでREST APIを使う前編

WordPressのREST APIのドキュメントを読んで分かりにくかった概念などがあったので調べてメモしておきます。

その前にREST APIアクセスするのにwp.apiFetch()を使っている箇所があります。
投稿ページではwp.apiFetch(JavaScriptファイル)がロードされていて使用することが出来ます。

以前の記事の続き的なページになってます。

WordPressのブロック開発メモ その11 REST API へのアクセス

WordPressが公開するREST API

WordPressはREST APIを公開してます。
もしWordPressを以下のURLで運用しているとしたら、

https://kurage

投稿一覧を取得するには以下のようにアクセスします。

https://kurage/wp-json/wp/v2/posts

するとレスポンスで以下のJSONが返ってきます。

デフォルトでは投稿データが最大10個返ってきます。
展開すると行数が多くなるのですべて閉じてます

クエリ

クエリで取得を制御することが出来ます。

一度に取得する最大数を指定

per_pageを指定すると取得する最大数を制限できます。

https://kurage/wp-json/wp/v2/posts?per_page=5

per_page5にすると最大5件まで取得します。

ちなみにドキュメントには最大100件みたいなことが書いてありましたが・・・。

ページを指定

pageを指定するとページネーションが実現出来ます。
1から数えます。

https://kurage/wp-json/wp/v2/posts?per_page=5&page=1

page2にしているので2ページ目を取得(per_pageも指定しているので最大5件までの条件も含む)

開始位置を指定

offsetを指定すると、何件目から取得するかを指定できます。

https://kurage/wp-json/wp/v2/posts?per_page=5&offset=3

offset3にすると、4件目から取得するようになります。
どうやら0から数えるみたいです。
per_pageも指定しているので0から数えて4件目から7件目までを取得します。

ソートを指定

orderを指定するとソート出来ます。
昇順の場合はasc, 降順の場合はdescを使用します。

またorderbyでソートするカラムを指定できます。

orderasc, orderbyidに指定すると
IDが小さい順で取得出来ます。

https://kurage/wp-json/wp/v2/posts?per_page=5&order=asc&orderby=id

orderdesc, orderbyidに指定すると
IDが大きい順で取得出来ます。

https://kurage/wp-json/wp/v2/posts?per_page=5&order=desc&orderby=id

投稿1件分のデータ

ところでこれまで投稿のデータのツリーノードを閉じていましたが、一つだけ展開してみましょう。

タイトルやスラグ、本文などのデータが確認出来ます。

フィールドを限定する

欲しいデータはIDとタイトルと本文だけでいいんだわ、という場合、_fieldsを指定して取得するフィールドを絞ることが出来ます。

 https://kurage/wp-json/wp/v2/posts?per_page=5&_fields=id,title,content

取得するフィールドをidtitlecontent限定しました。

大分すっきりしたので全部の件のノードを展開しました。

フィールドの階層

フィールドが階層化している場合はドットで区切ります。

https://kurage/wp-json/wp/v2/posts?per_page=1&_fields=id,title,content.protected

_fieldsid,title, content.protectedを指定するとこうなります。

毎回5件も取得すると多すぎるので1件に絞ってます。
contentにはrenderedprotectedの二つのフィールドがありましたが、今回はprotectedの方のみ取得出来てます。

関連情報?

https://kurage/wp-json/wp/v2/posts?per_page=1&_embed=1

_embed1にすると_embeddedauthorwp:termが追加されるようです。
authorは投稿した管理者、wp:term未分類などのターム。

_embedauthorにするとauthorだけ、
_embedwp:termにするとwp:termだけ取得出来るようです。

ただし、_fieldsとの併用は出来なかった(よくわかりません、面倒なので飛ばします)。

REST API のレスポンスにちゃっかり追加する

独自にREST APIを提供することもできます。

が、投稿などに項目を一つ二つ追加すれば済むようなこともあります。
例えば投稿にメモを追加したい場合、わざわざ独自のREST APIを公開しなくても投稿のフィールドにmemoなどを追加すれば済むことがあります。

投稿に独自のフィールドを追加する方法は二つあるようで、一つはregister_rest_field(), もう一つはregister_meta()があります。

前者が永続化を自分で行う、後者はメタデータに保存してくれるようです。

メタデータについては知っているものとして進めます。

その前にナンスを取得しておきます。

ブラウザのコンソールからJavaScriptを打って試していこうと思いますが、
その前にナンスを取得します。
以下のコードが成功すればナンスを取得出来ます。

await (await fetch('/wp-admin/admin-ajax.php?action=rest-nonce')).text()

register_rest_field()

投稿(post)にフィールドを追加するサンプルです。

add_action('rest_api_init', function(){

    register_rest_field(
        'post',
        'kurage_memo',
        [
            // 取得時
            'get_callback' => function($attr)
            {
                return get_post_meta($attr['id'], 'kurage_memo', true);
            },

            // 編集時
            'update_callback' => function($value, $post)
            {
                update_post_meta($post->ID, 'kurage_memo', $value);
            }
        ]
    );

});

register_rest_field()でフィールドを追加します。
第一引数は追加する対象(投稿の場合はpost)を指定します。
固定ページならpage、添付ファイルならattachmentを指定します。
ちなみに複数の対象に追加するなら配列で指定します。

register_rest_field( ['post', 'attachment', 'page'], ... );

第二引数で追加するフィールド名を指定します。
今回はkurage_memoを指定しました。

第三引数でオプションを指定します。
get_callbackはREST APIから取得する時(及び更新時)、
update_callbackはREST APIから更新する時に行う処理を指定します。

get_callbackはREST APIから取得する時(及び更新時)、
update_callbackはREST APIから更新する時に行う処理を指定します。

$postWP_Postのインスタンス、$attrは連想配列(JSONようのデータ?)で送られてきます。

早速実験。
投稿(ID=614)をREST APIから取得します。

await wp.apiFetch({path: '/wp/v2/posts/614'})

コールバック(get_callback)の戻り値がkurage_memoがフィールドに追加してありますが値は空文字になってます。

今度は投稿(ID=614)を編集します。
kurage_memoに値を設定して送ります。

await wp.apiFetch({path: '/wp/v2/posts/614', method: 'POST', data: { kurage_memo: 'めもめもめーも!' } });

編集するとコールバック(update_callback)が実行されDBに追加されます。
追加されたメタ情報はwp_postmetaテーブルを見てみます。

select * from wp_postmeta where meta_key like "kurage%";

もう一度投稿(ID=614)を見るとkurage_memoメタ情報が追加されていることが確認出来ます。

DBに値が設定してあると、REST APIのほうにもkurage_memoに値が設定してあることが確認出来ます。

スキーマ

スキーマを追加することもできます。

add_action('rest_api_init', function(){

    register_rest_field(
        'post',
        'kurage_memo',
        [
            // 取得時
            'get_callback' => function($attr)
            {
                return get_post_meta($attr['id'], 'kurage_memo', true);
            },

            // 編集時
            'update_callback' => function($value, $post)
            {
                update_post_meta($post->ID, 'kurage_memo', $value);
            },

            // スキーマを追加
            'schema' => [
                'description' => 'Kurage Attributes!',
                'type' => 'string'
            ]
        ]
    );

});

この場合、kurage_memoをstring型に制限出来ます。
うっかり数値123を送ったりするとrest_invalid_paramが発生したりします。

スキーマについては割合します。

register_meta()

さっきは自分でメタの追加しましたが、register_meta()を使うと自動的にメタとのマッピングをやってくれます。

REST APIのリクエスト/レスポンスの際にメタ情報を追加、取得すように登録します。

まず管理ページから投稿を新規作成しました。
IDは今回は614になってます。

ではregister_meta()で登録してみます。

add_action('rest_api_init', function(){

    register_meta(
        'post',
        'kurage_funny',
        [
            'type' => 'string',
            'description' => '笑って!',
            'single' => true,
            'show_in_rest' => true
        ]
    );

    register_meta(
        'post',
        'kurage_sad',
        [
            'type' => 'string',
            'description' => '泣いて!',
            'single' => true,
            'show_in_rest' => true
        ]
    );

});

register_meta()の第一引数はオブジェクトタイプです。
postcomment, user, termがあります。
今回は投稿に追加する例なのでpostを指定します。

第二引数はメタキー(今回はkurage_funny, kurage_sadの二つ)を指定します。
今回の設定ではpost(投稿や固定ページ、メディアなど)にメタキー(kurage_funnyとkurage_sad)を追加するように設定します。

REST APIから投稿を取得してみましょう。

await wp.apiFetch({path: '/wp/v2/posts/614'})

青線の部分を見てください。
metaの下にkurage_funnykurage_sadが追加されてます。
ただし、値は空文字になってます。

何故なら投稿の場合はデータベースのwp_postmetaテーブルに対応するメタ情報が無いからです。

では次は投稿を編集するリクエストをしてみましょう。

await wp.apiFetch({
        path: '/wp/v2/posts/614',
        method: 'POST',
        data:
        {
            meta:
            {
                kurage_funny: '電話にだれも出んわ!',
                kurage_sad: '財布なくした・・・'
            }
        }
});

metaに二つのメタキーを追加して更新します。
これを実行すると、まずwp_postmetaが更新されます。
どのように追加されたかテーブルを見てみます。

select post.ID, post.post_title, meta.meta_key, meta.meta_value
    from wp_posts as post left join wp_postmeta as meta
        on post.ID = meta.post_id
    where meta.meta_key like "%kurage%";

IDが614の投稿に2つのメタ情報が追加されているのが確認出来ます。
さてもう一度投稿を取得してみましょう。

await wp.apiFetch({path: '/wp/v2/posts/614'})

今度はwp_postmetaに二つのキーが存在するので取得出来るようになりました。

register_meta()はREST APIで取得、編集時にそのメタ情報を追加しますよと宣言しているだけで、この関数を実行してもそのままデータベースに反映されるわけではないんですね。

オブジェクトタイプ

オブジェクトタイプって何!?

今回のpostは投稿(/wp/v2/posts)や固定ページ(/wp/v2/pages)、メディア(/wp/v2/media)などのアクセスの際にメタ情報を追加するようにします。

ということは、

以下の何れのレスポンスにもメタ情報が追加されてます。

await wp.apiFetch({path: '/wp/v2/posts/605'})
await wp.apiFetch({path: '/wp/v2/media/469'})
await wp.apiFetch({path: '/wp/v2/pages/10'})

投稿も固定ページもメディアもデータベース上(wp_posts)に入っていて同じようなものですからね。

オブジェクトタイプを

termに指定するとカテゴリ(/wp/v2/categories)やタグ(/wp/v2/tags)にメタ情報を追加します。
userだとユーザー(/wp/v2/user)にメタ情報を追加します。
commentは省略します。

オブジェクトタイプをtermにすると以下のリクエストなどに追加されます。

await wp.apiFetch({path: '/wp/v2/categories/5'})
await wp.apiFetch({path: '/wp/v2/tags/11'})

オブジェクトタイプをuserにすると以下のリクエストなどに追加されます。

await wp.apiFetch({path: '/wp/v2/users/1'})

メタデータのサブタイプ

おいおい、postを指定したら投稿だけでなく固定ページやメディアにまで追加されるのはちょっと・・・
といった時にサブタイプを指定します。

add_action('rest_api_init', function(){

    register_meta(
        'post',
        'kurage_funny',
        [
            'type' => 'string',
            'description' => '笑って!',
            'single' => true,
            'show_in_rest' => true
        ]
    );

    register_meta(
        'post',
        'kurage_sad',
        [
            'type' => 'string',
            'description' => '泣いて!',
            'single' => true,
            'show_in_rest' => true,

            // 追加
            'object_subtype' => 'post'
        ]
    );

});

register_meta()のオプションでobject_subtypeを指定すると特定のサブタイプにしかメタは追加されません。
今回はkurage_sadに追加しているので、kurage_sadメタキーは以下の例の場合/wp/v2/posts/605のみに追加されます。

await wp.apiFetch({path: '/wp/v2/posts/605'})
await wp.apiFetch({path: '/wp/v2/media/469'})
await wp.apiFetch({path: '/wp/v2/pages/10'})

オブジェクトタイプがpostならサブタイトルは投稿(post), 固定ページ(page), メディア(media)、
オブジェクトタイプがtermならサブタイトルはカテゴリ(category), タグを(post_tag)
などが指定出来ます。

オブジェクトタイプがpostの場合、register_post_meta()を使うと一部省略出来ます。

    /*
    register_meta(
        'post',
        'kurage_sad',
        [
            'type' => 'string',
            'description' => '泣いて!',
            'single' => true,
            'show_in_rest' => true,
            'object_subtype' => 'page'
        ]
    );
    */

    // ↑と同じ
    register_post_meta(
        'page',
        'kurage_sad',
        [
            'type' => 'string',
            'description' => '泣いて!',
            'single' => true,
            'show_in_rest' => true,
        ]
    );

この場合オブジェクトタイプがpostに固定され、第一引数がサブタイプになります。
register_post_meta()のソースコードを覗くと以下のようになってます。

function register_post_meta( $post_type, $meta_key, array $args ) {
    $args['object_subtype'] = $post_type;

    return register_meta( 'post', $meta_key, $args );
}

term版にregister_term_meta()もあります。

register_rest_field()とregister_meta()の違い

register_rest_field()

  • フィールドはレスポンスのJSONオブジェクトに直接追加される
  • フィールドの値の更新と取得は自分で実装する必要がある。

register_meta()

  • フィールドはmetaプロパティの子として追加される。
  • フィールドの値の更新はupdate_metadata()で、取得はget_metadata()でいい感じに行われる。

register_rest_field()とregister_meta()の内部動作

どうしても内部が気になる人向けです。
この項は読む必要ありません。

単にregisterが何を意味するのか分かりにくかったのでソースコードを読んでみました。
その前にREST コントローラについて知る必要があります。

REST コントローラ

WordPressのREST APIは取得するタイプによってREST コントローラが定義されています。
例えば投稿を取得する/wp/v2/postsWP_REST_Posts_Controllerで定義してあります。
タクソノミーであればwp/v2/taxonomiesWP_REST_Taxonomies_Controllerに定義してあります。
コメントならWP_REST_Comments_Controllerといったように。

そしてこれらクラスの基底になっているのがWP_REST_Controller抽象クラスです。
CRUD系のメソッドや追加フィールド系のメソッドなどが定義されてはいます。

register_rest_field()の内部

この関数(register_rest_field())は追加フィールドの設定情報をグローバル変数の$wp_rest_additional_fieldsに追加しているにすぎません。
この値はREST コントローラから使用されます。

WP_REST_Controller

CRUD系ではアイテム(投稿やタクソノミーなど)の一覧を取得するget_items()や、IDを指定して一つだけアイテムを取得するget_item()
新しく作成するcreate_item()、更新するupdate_item()、削除するdelete_item()などが定義してあります。
ただし、これらのメソッドは抽象メソッドではなくWP_Errorを返す実体として定義されています。

追加フィールド系のメソッドでは
get_additional_fields(), add_additional_fields_to_object(), update_additional_fields_for_object()などが定義してあります。
今回省略しますが、追加フィールドのスキーマを追加するadd_additional_fields_schema()もあります。

WP_REST_Controller::get_additional_fields()

対象のオブジェクトタイプからグローバル変数の$wp_rest_additional_fieldsにアクセスして追加フィールドの設定情報を取得する。

WP_REST_Controller::add_additional_fields_to_object()

アイテムを取得時(get_item(), get_items())、
JSONオブジェクトにフィールドを追加、とその値を設定する。
その値は(get_callbackで指定したコールバック)から取得される。

WP_REST_Controller::update_additional_fields_for_object()

アイテムの作成時や更新時(create_item(), update_items())、追加フィールドの更新をコールバック関数(update_callback)を実行することでを行う。

WP_REST_Posts_Controller

投稿用のREST コントローラにはWP_REST_Posts_Controllerが用意されています。
CRUD系のメソッドがオーバーライドされています。
そしてこれらのメソッドから追加フィールドの設定情報が利用されます。

get_item()やget_items()らは追加するフィールドの値を取得する必要があります。
内部でコールバック(get_callback)を実行し、フィールドの値を取得してます。

create_item()やupdate_item()らは追加フィールドを変更しないといけません。
内部でコールバック(update_callback)を実行するようになってます。

register_meta()をより深く

この関数(register_meta())は単に引数の内容をグローバル変数の$wp_meta_keysに引数の内容を登録してるだけです。

ここで登録した設定はREST API コントローラから使用されます。
厳密にはメタキーを扱う抽象クラスWP_REST_Meta_Fieldsが主にメタキーを永続化します。

クラス図にするとこんな感じになってます。

型とか引数などはだいぶ省略してます。

Global

Globalはそのまま関数とグローバル変数を意味してます。
get_registered_meta_keys()register_meta()で登録したメタ情報を取得します。

WP_REST_Meta_Fields

このクラスはメタ情報を扱う抽象クラスです。
WP_REST_Meta_Fields::get_registerd_fields()get_registered_meta_keys()の登録情報を取得してごにょごにょしてます。

そしてWP_REST_Meta_Fields::get_value()
WP_REST_Meta_Fields::get_registered_fields()を参照したうえで、
get_metadata()から値を取得します。

WP_REST_Meta_Fields::update_value()
WP_REST_Meta_Fields::get_registered_fields()を参照したうえで、
がでデータベースに変更(update_metadata())を加えてます。
値が複数ある場合はadd_metadata()delete_metadata()でいい感じにやってくれてます。

WP_REST_Ppst_Meta_Fields

POST用のメタ情報はWP_REST_Meta_Fieldsを継承したこのクラスが使用されます。

WP_REST_Posts_Controller

REST API の投稿用コントローラです。
メタ情報の処理はWP_REST_Post_Meta_Fieldsのインスタンスを使用して行われます。
このインスタンスはコンストラクタで作成されmetaプロパティで保持されます。

作成、更新時、もしregister_meta()で登録された内容に適合してればWP_REST_Post_Meta_Fieldsを使ってメタ情報を操作(WP_REST_Meta_Fields::get_value()WP_REST_Meta_Fields::update_value())します。

発見?

https://developer.wordpress.org/rest-api/using-the-rest-api/discovery/

本家ドキュメントの意味がいまいち理解出来なかったのでいろいろいじって見つけた結果だけずらずら書いていきます。

リンクヘッダ

Link: ; rel="https://api.w.org/"

フロントエンドのページにリンクヘッダーを追加する・・・
どういう意味やねん!?

フロントページアクセスするとヘッダーのLinkにREST APIのアドレスのっけてるでってこと?

エレメント

Linkヘッダーと同等のものがフロントエンド ページのheadに・・・。

なるほど、DOMにlink要素があるからそれ使ってJavaScriptからREST APIのアドレス拾えるでってこと?

認証の検出、拡張機能の検出

WordPressのドメインから/wp-jsonにアクセスするとauthenticationnamespaceを発見できた。
routesもある。

つづく。

ちょっと長くなり過ぎたので次に。

certificate Docker Gutenberg Hyper-V openssl PHP React ReduxToolkit REST ubuntu WordPress オレオレ認証局 フレームワーク