【WordPress】WP REST APIで独自エンドポイントの作り方

【WordPress】WP REST APIで独自エンドポイントの作り方

WP REST APIとは

WP REST APIはWordPressに投稿した記事やデータにアクセスできるAPI機能のことです。WordPress 4.7以降より、標準で利用できるようになっています。

WP REST API 公式

https://ja.wp-api.org/

なぜ独自のエンドポイント?

WordPressに標準で設定されているエンドポイントで十分な情報が取得できるのですが、アイキャッチ画像やカスタムフィールドの情報を取得するのにちょっと工夫が必要だったり、逆に必要ない情報がたくさん入っていたりします。

これは非常に面倒臭い!
自分でAPIのカスタマイズはできないものか!と調べたらけっこう情報がありました。

参考にさせてもらったのはこちらの記事。

WP REST API V2にてカスタムエンドポイントを追加するまとめ

http://kayakuguri.github.io/blog/2016/07/12/wp-rest-api-custom-endpoint/

自分なりに使いやすいようにまとめたので今回はその手順をメモ。

準備

WP REST APIを扱うにあたって、WordPress側でいくつか設定とお作法的なものがあるのでそれを準備していきます。

functions.php

WordPressに独自エンドポイントを登録

/*-------------------------------------------*/
/* 独自エンドポイント登録
/*-------------------------------------------*/
function add_custom_endpoint()
{
  register_rest_route(
    //ネームスペース
    'custom/v0',
    //ベースURL
    '/test',
    //オプション
    [
      'methods'  =>  WP_REST_Server::READABLE,
      'permission_callback' => '__return_true',
      'callback' => 'fetch_test_data'
    ]
  );
}
add_action('rest_api_init', 'add_custom_endpoint');

rest_api_initというアクションフックでregister_rest_route関数を使ってエンドポイントの詳細を設定して登録します。ここではまずエンドポイントのURLを作ります。
第一引数のネームスペースにcustom/v0
第二引数のベースURLに/testとすると、
https://{ドメイン}/wp-json/custom/v0/testが新しいエンドポイントとして追加されます。
オプションはmethodsにWP_REST_Server::READABLEを指定します。
ここで指定できるメソッドは以下です。

  • WP_REST_Server::READABLE:GET
  • WP_REST_Server::CREATABLE:POST
  • WP_REST_Server::EDITABLE:POST, PUT, PATCH
  • WP_REST_Server::DELETABLE:DELETE
  • WP_REST_Server::ALLMETHODS:GET, POST, PUT, PATCH, DELETE

callbackにはコールバック関数の名前を指定します。コールバック関数の名前は後述で自分で作る関数の名前になるのでなんでも構いません。


*(2020.09.04追記)

permission_callbackがWP5.5から必須になりました。
ここでは'__return_true'として公開用のAPIとしてどこからでもアクセスできるようにしていますが、APIごとに権限などでアクセス制限したい場合は以下のように書きます。

function add_custom_endpoint()
{
  register_rest_route(
    //ネームスペース
    'custom/v0',
    //ベースURL
    '/test',
    //オプション
    [
      'methods'  =>  WP_REST_Server::READABLE,
      'permission_callback' => 'rest_permission', //関数名指定
      'callback' => 'fetch_test_data'
    ]
  );
}
add_action('rest_api_init', 'add_custom_endpoint');

//APIの権限をチェックする関数
function rest_permission(){
  return current_user_can('publish_posts');
}

こうすると、publish_postsの権限を持ったユーザーだけしかアクセスできないようにAPIを制限できます。プラグイン開発などで管理画面のみで扱うAPIを作るときや、何か特別なnonceやセッションなどをチェックしてアクセス制限させたいときなどに使えます。


続いてレスポンスを返却する際のお作法。

/*
* WP REST APIのレスポンス処理
* @param {String} $file_name ファイル名(拡張子なし)
* @param {Array} $param ajaxで受け取ったデータ
*/
function rest_response($file_name, $param = null) {
  $api_file = locate_template("api/${file_name}.php");
  $res      = !empty($api_file) ? include_once $api_file : [];
  $response = new WP_REST_Response($res);
  $response->set_status(200);
  return $response;
}

//テストデータ取得
function fetch_test_data($param)
{
  return rest_response('fetch-test-data', $param);
}

rest_responseという関数を作って、テーマ内にあるAPIの処理を書いたファイルからAPIで処理した内容を取得します。処理した内容はWP_REST_Responseという便利な関数を使って配列をJSONの形に整形して返却します。

*ここではひとまず動かすことを目的としていますので、全ての内容をstatus200として返すようにしていますが、実際にはWP_Errorクラスなどを使ってエラーハンドリングすることが必要になりますのでご注意ください。

先ほどコールバックに指定したfetch_test_dataという関数はrest_response関数を通して、{テーマURL}/api/fetch-test-data.phpというファイルからのデータを返すだけの関数です。具体的な内容はfetch-test-data.phpというファイルに書いていきます。ファイルが見つからない場合は空の配列を返すようにしておきます。

好き嫌いがあると思いますが、functions.phpに直接処理を書いていくとものすごく長くなって見通しが悪くなるのでファイル分割しています。もちろんファイル分割せずに直接ここに関数や返却する値を書いても問題ありません。

fetch-test-data.phpの中身は、とりあえず確認のために簡単な配列を返すものにしてみます。

<?php
return [
  'id'      => 1,
  'test'    => true,
  'message' => 'テストデータを取得しました'
];
?>

ここまででAPIとして動くようになっていますのでhttps://{ドメイン}/wp-json/custom/v0/testにアクセスしてみます。

{"id":1,"test":true,"message":"\u30c6\u30b9\u30c8\u30c7\u30fc\u30bf\u3092\u53d6\u5f97\u3057\u307e\u3057\u305f"}

このように返却データが表示されます。JSONに変換され日本語はエスケープ処理されているのでわかりにくいですが、ちゃんと情報が返ってきていることが確認できました。

ここまでできれば、あとはこの中身を取得したいデータに変更するだけで自分だけの独自APIとして動かすことができます。

実装例

どこを調べても大体このあたりで説明は終わり、あとはご自由にどうぞ。というパターンが多いので、この記事では実際に独自APIを使って記事を取得・表示するところまで実装してみます。

今回はTOPページに表示しているブログリストを独自エンドポイントを作って表示して見たいと思います。
完成したのはこちら。

Vue.js + WP REST APIで作ったDEMO

できるだけ簡単に済ませたいのでVue.js + axiosを使います。
最近のAPI連携はフレームワークを使った方が直感的で簡単ですね。もしjQueryを使う場合でもAPIから情報を取得する部分についてはそんなに変わりありませんので応用できます。jQuery使うのであればlodash.jsのテンプレートを使うと少し幸せになれそうです。

Lodash

https://lodash.com/

その他Vue.jsaxiosの詳しい使い方については今回は省略します。

PHP(独自API)

先ほど作ったfetch-test-data.phpを編集し、記事に必要なデータのみを抽出した配列を返すように作ります。

<?php
$post_data = [];
$post_args = [
  'post_type'      => 'post',
  'posts_per_page' => 30,
  'post_status'    => 'publish',
  'orderby'        => 'date',
  'order'          => 'DESC'
];
$post_query = new WP_Query( $post_args );
if ( $post_query->have_posts() ) :
  while ( $post_query->have_posts() ) :
    $post_query->the_post();
    global $post;
    //著者情報
    $author          = get_userdata($post->post_author);
    $author_id       = $author->ID;
    $author_nicename = get_the_author_meta('user_nicename');
    //カスタムフィールドの画像を取得
    $avatar_src      = scf_auther_src($author_id, 'cf_writer_avatar', 'thumbnail');
    //カテゴリ取得
    $category   = get_the_category();
    $cat_name   = $category[0]->cat_name;
    $cat_slug   = $category[0]->slug;
    //タグ
    $tags       = get_the_tags();
    $tags_array = [];
    foreach ($tags as $tag) {
      $tags_array[] = [
        'tag_id'    => $tag->term_id,
        'tag_name'  => $tag->name
      ];
    }

    $post_data[] = [
      //記事ID
      'id'            => $post->ID,
      //公開日
      'time'          => get_the_time('Y.m.d'),
      //画像URL
      'image_src'     => !empty(get_eyecatch_src('medium')) ? get_eyecatch_src('medium') : get_theme_file_uri('assets/images/default-post.jpg'),
      //執筆者
      'author_name'   => $author_nicename,
      //執筆者の画像
      'avatar_src'    => $avatar_src,
      'title'         => wp_strip_all_tags(remove_two_space($post->post_title), true),
      'category'      => $cat_name,
      'category_slug' => $cat_slug,
      //タグ
      'tags'          => $tags_array,
      //リンク
      'link'          => get_the_permalink()
    ];
  endwhile;
  wp_reset_postdata();
endif;

return $post_data;
?>

これでAPIにhttps://{ドメイン}/wp-json/custom/v0/testにアクセスすると最大30記事分の最新記事の情報がJSONで取得できるようになりました。

scf_auther_srcget_eyecatch_srcという関数は独自で作った関数です。環境に合わせて情報取得の処理は書き換えてください。

HTML

<div>
  <section>
    <h2>POSTS by REST API</h2>
    <posts/></posts>
  </section>
</div>
<script>
    var WP_API_Settings = {
      root       : "<?php echo esc_url_raw(rest_url())?>",
      rest_nonce : "<?php echo wp_create_nonce('wp_rest')?>"
    };
</script>

htmlはこれだけ。CSSは長くなるので省略します。記事のループ部分はAPIから情報を受け取ってVue.jsのテンプレートで生成します。scriptタグにはREST APIのルートURLと、後述で扱うクッキー認証のためのnonceの2つを設定したオブジェクトをJS内で扱うために変数にしておきます。

Vueテンプレート

<template>
  <div class="group">
    <div class=list">
      <a class="item" v-for="post in posts" :href="post.link" :class="categoryClassName(post)" :key="post.id">
        <time class="date">{{post.time}}</time>
        <div class="pic">
          <img
          :src="post.image_src"
          :alt="post.title">
        </div>
        <!-- /.pic -->
        <div class="content">
          <div class="avatar">
            <span class="avatar-name">{{post.author_name}}</span>
            <div class="avatar-icon">
              <img
              :src="post.avatar_src"
              :alt="post.title"
              >
            </div>
            <!-- /.avatar-icon -->
          </div>
          <!-- /.avatar -->
          <span class="cat" :class="categoryClassName(post)">{{post.category}}</span>
          <p class="title">{{post.title}}</p>
          <ul class="tags">
            <li class="tag" v-for="tag in post.tags" :key="tag.tag_id">{{tag.tag_name}}</li>
          </ul>
          <!-- /.tags -->
        </div>
        <!-- /.content -->
      </a>
      <!-- /.item -->
    </div>
    <!-- /.list -->
  </div>
  <!-- /.group -->
</template>

ここでのポイントはv-forの使い方です。

  • すべての記事情報はpostsという値に配列で入れてv-forでループして表示する。
  • タグも配列なのでv-forでループさせて表示させる

JavaScript

実際はHTMLとJavaScriptをひとつのcomponentとして同じ.vueファイルにまとめて書きますが、説明の都合上分けて書いています。

<script>
  import axios from 'axios'
  const wp_rest = axios.create({
    baseURL: WP_API_Settings.root,
    headers: { 'X-WP-Nonce': WP_API_Settings.rest_nonce }
  });

  export default {
    data() {
      return {
        posts : null
      }
    },
    created() {
      wp_rest.get('custom/v0/test?_envelope')
      .then((res) => {
        if(Array.isArray(res.data.body) && res.data.body[0]){
          this.posts = res.data.body;
        }
      })
      .catch(res => {
        if('console' in window){
         console.error(res);
       }
     })
    },
    methods : {

      /*
      * カテゴリー名のClassを付ける
      * @param {Object} post 記事情報
      */
      categoryClassName(post) {
        if(('category_slug' in post) && post.category_slug){
          return post.category_slug;
        }
      }

    }
  }
</script>

JavaScriptはめちゃくちゃシンプルです。
axiosで先ほど作ったAPIにアクセスして、それをpostsにそのまま入れるだけです。これだけでVue.jsが自動的にv-forで表示させてくれます。

categoryClassNameメソッドはカテゴリースラッグをclass名として追加するだけのシンプルな関数です。これは記事の色分けに使っているだけなので特に必要なければ削除して大丈夫です。

ここでわかりにくいかなと思うのはajaxの設定です。
ポイントとしては、以下の2点。

  • ヘッダーにX-WP-NonceというWordPressのクッキー認証を入れる
  • リスエストURLに_envelopeパラメータを付ける

まずX-WP-Nonceですが、今回のようなGETメソッドのみのエンドポイントの場合は特に必要ないのですが、POSTメソッドを扱う場合は必須になります。
僕は忘れがちなのでAPIを扱う時は必ずセットで設定しておくようにしています。

次に_envelopeパラメータについて。
これは以前、独自のWP REST APIエンドポイントを作ったときに、なぜかカスタムフィールドの情報だけが取得できない!というトラブルが起き、このパラメータを付けることで解決した、ということがあったので付けるようになりました。レスポンスデータがbodyに入って、レスポンスとしても扱いやすいため必ず付けています。

どちらも付けないと表示されないってことはないと思いますので、必要ない方は外していただいて良いと思います。僕はWP REST APIの取得方法は統一しておきたいのでこの書き方に落ち着きました。

以上で完成です!

Vue.js + WP REST APIで作ったDEMO

さいごに

これまでwp-ajax.phpを使ってajax処理をしていましたが、新規で作るものはWP REST APIを使うようにしています。wp-ajax.phpと比べてレスポンスの速さが半分くらいになるのでそれだけでも使う価値があると思います。

ぜひ楽しいWP REST APIライフを!