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 Controllerを実装してみる。
WordPress本体のREST API実装はコントローラで実装してあり、
その実体はWP_REST_Controller
の派生クラスです。
例えば投稿のREST API(/wp/v2/posts
)はWP_REST_Posts_Controller
クラスで実装されてます。
独自のコントローラもこのWP_WP_REST_Controller
クラスを実装して行います。
ただし、結構分かりづらい・・・。
WP_REST_Controller
にはCRUDやその権限(パーミッション)のひな形となるメソッド一式があります。
例えばアイテム一覧を取得する場合は、アイテム一覧を取得するget_items()
と、そのパーミッションを返すget_items_permissions_check()
が定義されています。
アイテム取得一覧を実装する場合はこれら2つを実装します。
同じように追加したり削除したり変更したりするメソッドのペアが用意されています。
まずはコントローラを実装する前に、データを永続化する仕組みを作ります。
とにかく手抜きをするため、WordPressのオプション(get_option()
, updateOption()
)を利用することにします。
この方法は膨大なデータには向いてません。
あくまで最低限の実装でコントローラを実装するためのものです。
class Osakanas
{
public int $id;
public string $label;
public string $description;
public int $min;
public int $max;
}
class OsakanaService
{
private const KURAGE_OPTION = 'KURAGE::Osakanas';
private static function getOption()
{
return get_option(self::KURAGE_OPTION, []);
}
private static function saveOption($osakanas = [])
{
update_option(self::KURAGE_OPTION, $osakanas);
}
public static function clear()
{
self::saveOption();
}
public static function getOsakanas()
{
$items = self::getOption();
return $items;
}
public static function getOsakana(int $id)
{
$items = self::getOption();
foreach($items as $item)
{
if($item->id === $id)
{
return $item;
}
}
return null;
}
public static function addOsakana(stdClass|Osakanas $obj)
{
$items = self::getOption();
// 追加したアイテムのIDを決定
$id = max( [...array_map(fn($v) => $v->id, $items), 0]) + 1;
// インスタンス作成
$osakana = new Osakanas();
foreach((array)$obj as $key => $value) $osakana->$key = $value;
$osakana->id = $id;
// アイテムの追加
$items = [...$items, $osakana];
self::saveOption($items);
return $id;
}
public static function editOsakana($data)
{
$data = $data instanceof Osakanas ? (array)$data : $data;
$id = $data['id'] ?? null;
if(!$id)
{
return;
}
$items = self::getOption();
foreach($items as $item)
{
if($item->id === $id)
{
foreach(['label', 'description', 'min', 'max'] as $key)
{
if(isset($data[$key]))
{
$item->$key = $data[$key];
}
}
}
}
self::saveOption($items);
}
public static function deleteOsakana($id)
{
$items = self::getOption();
$s = [];
foreach($items as $item)
{
if($item->id !== $id)
{
$s[] = $item;
}
}
self::saveOption($s);
}
public static function initialize()
{
OsakanaService::clear();
$o = new Osakanas();
$o->label = 'ika';
$o->description = 'すみはくよ!';
$o->min = 3;
$o->max = 10;
OsakanaService::addOsakana($o);
$o = new Osakanas();
$o->label = 'tako';
$o->description = 'なぐるよ!';
$o->min = 0;
$o->max = 10;
OsakanaService::addOsakana($o);
$o = new Osakanas();
$o->label = 'same';
$o->description = 'かんじゃうぞ!';
$o->min = 100;
$o->max = 1000;
OsakanaService::addOsakana($o);
OsakanaService::editOsakana(['id' => 2, 'label' => 'ikasama', 'description' => '賭けないか!?']);
$items = OsakanaService::getOsakanas();
assert($items[1]->label === 'ikasama', '二番目のアイテムのラベル変更');
}
}
見てわかる通り、オプションに対してCRUD
する単純なサービスクラスを実装してます。
エンティティとなるクラスはOsakanas
です。
class Osakanas
{
public int $id;
public string $label;
public string $description;
public int $min;
public int $max;
}
識別用のid
、後は適当に文字列型のlabel
,description
と、
整数型のmin
,max
があるクラスです。
まずはinitialize()
メソッドを最初の1回だけ実行します。
3つのアイテムを追加します。
OsakanaService::initialize();
これでデータの初期化は完了です。
初期化後の一覧を取得してみましょう。
$items = OsakanaService::getOsakanas();
以後Osakanas
及びOsakanaService
の両クラスは存在するものとして進めます。
アイテム一覧を取得する。
まずはアイテム一覧を取得します。
REST APIのルートは以下のようにします。
/wp/v2/osakanas
HTTPメソッドはGET
です。
以下は最小限の実装です。
class OsakanasController extends WP_REST_Controller
{
public function __construct()
{
$this->namespace = 'wp/v2';
$this->rest_base = 'osakanas';
}
public function register_routes()
{
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
[
'methods' => WP_REST_Server::READABLE,
'callback' => [$this, 'get_items'],
'permission_callback' => [$this, 'get_items_permissions_check'],
]
);
}
public function get_items($request)
{
$items = OsakanaService::getOsakanas();
$results = [];
foreach( $items as $item )
{
$data = $this->prepare_item_for_response($item, $request);
$results[] = $this->prepare_response_for_collection($data);
}
return rest_ensure_response($results);
}
public function get_items_permissions_check($request)
{
return true;
}
public function prepare_item_for_response($item, $request)
{
// オブジェクトで受け取ったアイテムを連想配列に変換。
$data = (array)$item;
/**
* レスポンスの作成、必要であればレスポンスの操作
*/
$response = rest_ensure_response($data);
// レスポンスを返す
return $response;
}
}
// ルートの登録
add_action('rest_api_init', function(){
$controller = new OsakanasController();
$controller->register_routes();
});
ではREST API叩いてみます。
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
[
'methods' => WP_REST_Server::READABLE,
'callback' => [$this, 'get_items'],
'permission_callback' => [$this, 'get_items_permissions_check'],
]
);
methods
にはGET
を指定します。
callback
とpermission_callback
にはそれぞれget_items()
とget_items_permissions_check()
を指定しています。
これら二つのメソッドはWP_REST_Controller
ですでに定義してあります。
エラーを返すだけの実装ですが、これをひな形としてオーバーライドする形になります。
誰でもアクセスできるようパーミッションはtrue
を返してます。
またget_items()
はアイテム一覧を取得して返すメソッドですが、
全てのアイテムをprepare_item_for_response()
に渡して変換してます。
このメソッドはオブジェクトのアイテムを連想配列に変換します。
連想配列を作る過程でフィールドをフィルタリングして返すフィールドを限定したり、
又はフィールドを追加したりすることもできます。
次の例で紹介します。
スキーマ及びフィルタリングの実装
次にget_item_schema()
メソッドをオーバーライドしてスキーマを取得出来るようにします。
class OsakanasController extends WP_REST_Controller
{
public function __construct()
{
$this->namespace = 'wp/v2';
$this->rest_base = 'osakanas';
}
public function register_routes()
{
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
[
'methods' => WP_REST_Server::READABLE,
'callback' => [$this, 'get_items'],
'permission_callback' => [$this, 'get_items_permissions_check'],
]
);
}
public function get_items($request)
{
$items = OsakanaService::getOsakanas();
$results = [];
foreach( $items as $item )
{
$data = $this->prepare_item_for_response($item, $request);
$results[] = $this->prepare_response_for_collection($data);
}
return rest_ensure_response($results);
}
public function get_items_permissions_check($request)
{
return true;
}
public function prepare_item_for_response($item, $request)
{
// レスポンス用配列の準備。
$data = [];
/**
* フィールドによるフィルタリング
*/
$fields = $this->get_fields_for_response($request);
if( rest_is_field_included('id', $fields) )
{
$data['id'] = $item->id;
}
if( rest_is_field_included('label', $fields) )
{
$data['label'] = $item->label;
}
if( rest_is_field_included('description', $fields) )
{
$data['description'] = $item->description;
}
if( rest_is_field_included('min', $fields) )
{
$data['min'] = $item->min;
}
if( rest_is_field_included('max', $fields) )
{
$data['max'] = $item->max;
}
/**
* 追加フィールドの追加
*/
$data = $this->add_additional_fields_to_object($data, $request);
/**
* コンテキストによるフィルタリング
*/
$context = $request->get_param('context') ?? 'view';
$data = $this->filter_response_by_context($data, $context);
/**
* レスポンスの作成、必要であればレスポンスの操作
*/
$response = rest_ensure_response($data);
// レスポンスを返す
return $response;
}
public function get_item_schema()
{
$this->schema = [
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'kurage',
'type' => 'object',
'properties' => [
'id' =>
[
'description' => 'ID',
'type' => 'integer',
],
'label' =>
[
'description' => 'あんたの表示名は?',
'type' => ['string', 'null'],
],
'description' =>
[
'description' => 'てきとーに自己紹介して!',
'type' => ['string', 'null'],
],
'min' =>
[
'description' => '1日のあくびの最小回数は?',
'type' => 'integer',
],
'max' =>
[
'description' => '1日のあくびの最大回数は?',
'type' => 'integer',
]
]
];
return $this->add_additional_fields_schema($this->schema);
}
}
// ルートの登録
add_action('rest_api_init', function(){
register_rest_field(
'kurage',
'ikasan',
[
'get_callback' => fn() => '墨はくどー',
'update_callback' => fn($value) => null,
// ここにschemaを設定していると、add_additional_fields_schema()を呼び出した時にマージされる。
'schema' =>
[
'description' => 'I have 10 legs.',
'type' => 'string'
]
]
);
register_rest_field(
'kurage',
'takosan',
[
'get_callback' => fn() => 'タコ殴りだべー!',
'update_callback' => fn($value) => null,
// ここにschemaを設定していると、add_additional_fields_schema()を呼び出した時にマージされる。
'schema' =>
[
'description' => 'I have 8 legs.',
'type' => 'string'
]
]
);
$controller = new OsakanasController();
$controller->register_routes();
});
このようにget_item_schema()
を追加してます。
/wp/v2/osakanas
こんな結果になりました。
追加フィールド
追加フィールド「ikasan
とtakosan
」が各アイテムに追加されて取得出来ていることがわかります。
以下の部分が追加フィールドを追加しているところです。
/**
* 追加フィールドの追加
*/
$data = $this->add_additional_fields_to_object($data, $request);
フィールドフィルター
クエリパラメータに_fields
を渡すとその項目だけ取得出来ていることが確認できます。
/wp/v2/osakanas?_fields=label,min,ikasan
この部分を実装しているのが以下のコードです。
$fields = $this->get_fields_for_response($request);
if( rest_is_field_included('id', $fields) )
{
$data['id'] = $item->id;
}
if( rest_is_field_included('label', $fields) )
{
$data['label'] = $item->label;
}
if( rest_is_field_included('description', $fields) )
{
$data['description'] = $item->description;
}
if( rest_is_field_included('min', $fields) )
{
$data['min'] = $item->min;
}
if( rest_is_field_included('max', $fields) )
{
$data['max'] = $item->max;
}
コンテキスト
コンテキストの実装例です。
class OsakanasController extends WP_REST_Controller
{
public function __construct()
{
$this->namespace = 'wp/v2';
$this->rest_base = 'osakanas';
}
public function register_routes()
{
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
[
'methods' => WP_REST_Server::READABLE,
'callback' => [$this, 'get_items'],
'permission_callback' => [$this, 'get_items_permissions_check'],
'args' =>
[
// コンテキストの追加
'context' => $this->get_context_param(['default' => 'view'])
]
]
);
}
public function get_items($request)
{
$items = OsakanaService::getOsakanas();
$results = [];
foreach( $items as $item )
{
$data = $this->prepare_item_for_response($item, $request);
$results[] = $this->prepare_response_for_collection($data);
}
return rest_ensure_response($results);
}
public function get_items_permissions_check($request)
{
return true;
}
public function prepare_item_for_response($item, $request)
{
// レスポンス用配列の準備。
$data = [];
/**
* フィールドによるフィルタリング
*/
$fields = $this->get_fields_for_response($request);
if( rest_is_field_included('id', $fields) )
{
$data['id'] = $item->id;
}
if( rest_is_field_included('label', $fields) )
{
$data['label'] = $item->label;
}
if( rest_is_field_included('description', $fields) )
{
$data['description'] = $item->description;
}
if( rest_is_field_included('min', $fields) )
{
$data['min'] = $item->min;
}
if( rest_is_field_included('max', $fields) )
{
$data['max'] = $item->max;
}
/**
* 追加フィールドの追加
*/
$data = $this->add_additional_fields_to_object($data, $request);
/**
* コンテキストによるフィルタリング
*/
$context = $request->get_param('context') ?? 'view';
$data = $this->filter_response_by_context($data, $context);
/**
* レスポンスの作成、必要であればレスポンスの操作
*/
$response = rest_ensure_response($data);
// レスポンスを返す
return $response;
}
public function get_item_schema()
{
$this->schema = [
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'kurage',
'type' => 'object',
'properties' => [
'id' =>
[
'description' => 'ID',
'type' => 'integer',
'context' => ['view']
],
'label' =>
[
'description' => 'あんたの表示名は?',
'type' => ['string', 'null'],
'context' => ['view']
],
'description' =>
[
'description' => 'てきとーに自己紹介して!',
'type' => ['string', 'null'],
'context' => ['view', 'fire']
],
'min' =>
[
'description' => '1日のあくびの最小回数は?',
'type' => 'integer',
'context' => ['view']
],
'max' =>
[
'description' => '1日のあくびの最大回数は?',
'type' => 'integer',
'context' => ['view', 'fire']
]
]
];
return $this->add_additional_fields_schema($this->schema);
}
}
// ルートの登録
add_action('rest_api_init', function(){
register_rest_field(
'kurage',
'ikasan',
[
'get_callback' => fn() => '墨はくどー',
'update_callback' => fn($value) => null,
// ここにschemaを設定していると、add_additional_fields_schema()を呼び出した時にマージされる。
'schema' =>
[
'description' => 'I have 10 legs.',
'type' => 'string',
'context' => ['view', 'fire']
]
]
);
register_rest_field(
'kurage',
'takosan',
[
'get_callback' => fn() => 'タコ殴りだべー!',
'update_callback' => fn($value) => null,
// ここにschemaを設定していると、add_additional_fields_schema()を呼び出した時にマージされる。
'schema' =>
[
'description' => 'I have 8 legs.',
'type' => 'string',
'context' => ['view']
]
]
);
$controller = new OsakanasController();
$controller->register_routes();
});
栗栄パラメータにcontext=fire
を渡します。
/wp/v2/osakanas?context=fire
取得出来たフィールドは「description
, ikasan
, max
」の3つです。
コンテキストを実装するには以下のようにします。
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
[
'methods' => WP_REST_Server::READABLE,
'callback' => [$this, 'get_items'],
'permission_callback' => [$this, 'get_items_permissions_check'],
'args' =>
[
// コンテキストの追加
'context' => $this->get_context_param(['default' => 'view'])
]
]
);
ルートの設定でargs
にcontext
を追加します。
context
の値はview
かfire
のどちらかである必要がある設定です。
次にスキーマにコンテキストを指定します。
public function get_item_schema()
{
$this->schema = [
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'kurage',
'type' => 'object',
'properties' => [
'id' =>
[
'description' => 'ID',
'type' => 'integer',
'context' => ['view']
],
'label' =>
[
'description' => 'あんたの表示名は?',
'type' => ['string', 'null'],
'context' => ['view']
],
'description' =>
[
'description' => 'てきとーに自己紹介して!',
'type' => ['string', 'null'],
'context' => ['view', 'fire']
],
'min' =>
[
'description' => '1日のあくびの最小回数は?',
'type' => 'integer',
'context' => ['view']
],
'max' =>
[
'description' => '1日のあくびの最大回数は?',
'type' => 'integer',
'context' => ['view', 'fire']
]
]
];
return $this->add_additional_fields_schema($this->schema);
}
全てのフィールドにコンテキストを指定してます。
この例ではコンテキストは全てのフィールドがview
を持っており、
description
及びmax
がfire
を持ってます。
ちなみにview
もfire
も適当な名前を付けています。
以下のように追加フィールドにもコンテキストを指定してます。
register_rest_field(
'kurage',
'ikasan',
[
'get_callback' => fn() => '墨はくどー',
'update_callback' => fn($value) => null,
// ここにschemaを設定していると、add_additional_fields_schema()を呼び出した時にマージされる。
'schema' =>
[
'description' => 'I have 10 legs.',
'type' => 'string',
'context' => ['view', 'fire']
]
]
);
register_rest_field(
'kurage',
'takosan',
[
'get_callback' => fn() => 'タコ殴りだべー!',
'update_callback' => fn($value) => null,
// ここにschemaを設定していると、add_additional_fields_schema()を呼び出した時にマージされる。
'schema' =>
[
'description' => 'I have 8 legs.',
'type' => 'string',
'context' => ['view']
]
]
);
このようにikasan
だけfire
を指定してます。
/wp/v2/osakanas?context=fire
はコンテキストにfire
を持ったフィールドだけ取得するものです。
ちなみにコンテキストの名前は自由に追加出来、コンテキストの名前の分だけargs
のcontext
に設定されます($this->get_context_param()の仕事
)。
記事を作成する
REST APIのルートは、
/wp/v2/osakanas
HTTPメソッドはPOST
を使い、今度は記事を追加してみます。
create_item()
とcreate_item_permissions_check()
をオーバーラードします。
今回はOsakanasController
クラスのみ載せます。
class OsakanasController extends WP_REST_Controller
{
public function __construct()
{
$this->namespace = 'wp/v2';
$this->rest_base = 'osakanas';
}
public function register_routes()
{
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
[
[
'methods' => WP_REST_Server::READABLE,
'callback' => [$this, 'get_items'],
'permission_callback' => [$this, 'get_items_permissions_check'],
'args' =>
[
'context' => $this->get_context_param(['default' => 'view'])
]
],
[
'methods' => WP_REST_Server::CREATABLE,
'callback' => [$this, 'create_item'],
'permission_callback' => [$this, 'create_item_permissions_check'],
// ここ注目。スキーマを使用するよ!
'args' => $this->get_endpoint_args_for_item_schema(WP_REST_Server::CREATABLE)
]
]
);
}
public function get_items($request)
{
$items = OsakanaService::getOsakanas();
$results = [];
foreach( $items as $item )
{
$data = $this->prepare_item_for_response($item, $request);
$results[] = $this->prepare_response_for_collection($data);
}
return rest_ensure_response($results);
}
public function get_items_permissions_check($request)
{
return true;
}
public function prepare_item_for_response($item, $request)
{
// レスポンス用配列の準備。
$data = [];
/**
* フィールドによるフィルタリング
*/
$fields = $this->get_fields_for_response($request);
if( rest_is_field_included('id', $fields) )
{
$data['id'] = $item->id;
}
if( rest_is_field_included('label', $fields) )
{
$data['label'] = $item->label;
}
if( rest_is_field_included('description', $fields) )
{
$data['description'] = $item->description;
}
if( rest_is_field_included('min', $fields) )
{
$data['min'] = $item->min;
}
if( rest_is_field_included('max', $fields) )
{
$data['max'] = $item->max;
}
/**
* 追加フィールドの追加
*/
$data = $this->add_additional_fields_to_object($data, $request);
/**
* コンテキストによるフィルタリング
*/
$context = $request->get_param('context') ?? 'view';
$data = $this->filter_response_by_context($data, $context);
/**
* レスポンスの作成、必要であればレスポンスの操作
*/
$response = rest_ensure_response($data);
// レスポンスを返す
return $response;
}
public function create_item_permissions_check($request)
{
return current_user_can('administrator');
}
public function create_item($request)
{
// リクエストからオブジェクトを作成
$item = $this->prepare_item_for_database($request);
if(is_wp_error($item))
{
return $item;
}
// 追加
$id = OsakanaService::addOsakana($item);
// 追加したものをもう一度取得(今回は意味がない)
$osakana = OsakanaService::getOsakana($id);
// 追加フィールドもアップデート
$f = $this->update_additional_fields_for_object($osakana, $request);
if(is_wp_error($f))
{
return $f;
}
// アイテム作成後返信用のアイテムの準備
$data = $this->prepare_item_for_response($osakana, $request);
// レスポンス
$response = rest_ensure_response($data);
$response->set_status(201);
return $response;
}
protected function prepare_item_for_database($request)
{
$item = new stdClass;
if($request->has_param('id'))
{
$item->id = $request->get_param('id');
}
if($request->has_param('label'))
{
$item->label = $request->get_param('label');
}
if($request->has_param('description'))
{
$item->description = $request->get_param('description');
}
if($request->has_param('min'))
{
$item->min = $request->get_param('min');
}
if($request->has_param('max'))
{
$item->max = $request->get_param('max');
}
return $item;
}
public function get_item_schema()
{
$this->schema = [
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'kurage',
'type' => 'object',
'properties' => [
'id' =>
[
'description' => 'ID',
'type' => 'integer',
'context' => ['view']
],
'label' =>
[
'description' => 'あんたの表示名は?',
'type' => ['string', 'null'],
'context' => ['view']
],
'description' =>
[
'description' => 'てきとーに自己紹介して!',
'type' => ['string', 'null'],
'context' => ['view', 'fire']
],
'min' =>
[
'description' => '1日のあくびの最小回数は?',
'type' => 'integer',
'context' => ['view']
],
'max' =>
[
'description' => '1日のあくびの最大回数は?',
'type' => 'integer',
'context' => ['view', 'fire']
]
]
];
return $this->add_additional_fields_schema($this->schema);
}
}
追加と取得です。
追加するためのルートを指定します。
register_rest_route()
は配列で複数のルートも指定できます。
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
[
[
'methods' => WP_REST_Server::READABLE,
'callback' => [$this, 'get_items'],
'permission_callback' => [$this, 'get_items_permissions_check'],
'args' =>
[
'context' => $this->get_context_param(['default' => 'view'])
]
],
[
'methods' => WP_REST_Server::CREATABLE,
'callback' => [$this, 'create_item'],
'permission_callback' => [$this, 'create_item_permissions_check'],
// ここ注目。スキーマを使用するよ!
'args' => $this->get_endpoint_args_for_item_schema(WP_REST_Server::CREATABLE)
]
]
);
methods
はPOST
に、callback
とpermission_callback
はそれぞれcreate_item()
及びcreate_item_permissions_check()
を定義します。
どちらもWP_REST_Controller
クラスにすでに定義してあり、それをオーバーライドします。
リクエストの検証のため以下のメソッドの戻り値をargs
に指定します。
'args' => $this->get_endpoint_args_for_item_schema(WP_REST_Server::CREATABLE)
このメソッドはget_item_schema()
か返したスキーマをargs
に指定出来る形にして返してくれます。
パーミッションは管理者のみ追加出来るようになってます。
権限についてはcurrent_user_can()
を調べてください。
public function create_item_permissions_check($request)
{
return current_user_can('administrator');
}
記事を追加する時などは記事の所有者であるかをチェックする必要がありますが、
今回は管理者しか使わないこと前提なのでこの設定です。
public function create_item($request)
{
// リクエストからオブジェクトを作成
$item = $this->prepare_item_for_database($request);
if(is_wp_error($item))
{
return $item;
}
// 追加
$id = OsakanaService::addOsakana($item);
// 追加したものをもう一度取得(今回は意味がない)
$osakana = OsakanaService::getOsakana($id);
// 追加フィールドもアップデート
$f = $this->update_additional_fields_for_object($osakana, $request);
if(is_wp_error($f))
{
return $f;
}
// アイテム作成後返信用のアイテムの準備
$data = $this->prepare_item_for_response($osakana, $request);
// レスポンス
$response = rest_ensure_response($data);
$response->set_status(201);
return $response;
}
追加時、レスポンスを返しますがその時もまたprepare_item_for_response()
やupdate_additional_fields_for_object()
を通していることに注目してください。
まず以下のメソッドです。
$item = $this->prepare_item_for_database($request);
このメソッドもまたWP_REST_Controller
クラスで定義してありオーバーライドして使います。
リクエストからstdClass
オブジェクトを作成して返します。
以下は追加フィールドに更新するためのものです。
$f = $this->update_additional_fields_for_object($osakana, $request);
追加フィールドregister_rest_field()
のupdate_callback
で指定したコールバックを実行します。
つまり、アイテムを保存するたびに追加フィールド一覧も保存するようにしているわけです。
最後に
これら追加フィールドやフィルタリング等の実装は必ずしも必要ないでしょう。
また今回はページネーション(page
,per_page
)を実装してません。
大量のアイテムを前提としていないためです。
必要な場合はルート登録の時「$this->get_collection_params()
」を使って実装してください。
最後に「編集、特定のIDのアイテムの取得、削除」も実装したコードを載せます。
「一覧表示、追加」と違う点はIDを指定して単一のアイテム操作だという点です。
よってルートにはIDを指定します(以下の例はIDを12345
とする場合)。
/wp/v2/osakanas/12345
class OsakanasController extends WP_REST_Controller
{
public function __construct()
{
$this->namespace = 'wp/v2';
$this->rest_base = 'osakanas';
}
public function register_routes()
{
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
[
[
'methods' => WP_REST_Server::READABLE,
'callback' => [$this, 'get_items'],
'permission_callback' => [$this, 'get_items_permissions_check'],
'args' =>
[
'context' => $this->get_context_param(['default' => 'view'])
]
],
[
'methods' => WP_REST_Server::CREATABLE,
'callback' => [$this, 'create_item'],
'permission_callback' => [$this, 'create_item_permissions_check'],
// ここ注目。スキーマを使用するよ!
'args' => $this->get_endpoint_args_for_item_schema(WP_REST_Server::CREATABLE)
]
]
);
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/(?P<id>[\d]+)',
[
'args' =>
[
'id' =>
[
'description' => 'ID',
'type' => 'integer'
]
],
[
'methods' => WP_REST_Server::READABLE,
'callback' => [$this, 'get_item'],
'permission_callback' => [$this, 'get_item_permissions_check'],
'args' =>
[
'context' => $this->get_context_param(['default' => 'view'])
]
],
[
'methods' => WP_REST_Server::EDITABLE,
'callback' => [$this, 'update_item'],
'permission_callback' => [$this, 'update_item_permissions_check'],
'args' => $this->get_endpoint_args_for_item_schema(WP_REST_Server::EDITABLE)
],
[
'methods' => WP_REST_Server::DELETABLE,
'callback' => [$this, 'delete_item'],
'permission_callback' => [$this, 'delete_item_permissions_check']
]
]
);
}
public function get_items($request)
{
$items = OsakanaService::getOsakanas();
$results = [];
foreach( $items as $item )
{
$data = $this->prepare_item_for_response($item, $request);
$results[] = $this->prepare_response_for_collection($data);
}
return rest_ensure_response($results);
}
public function get_items_permissions_check($request)
{
return true;
}
public function prepare_item_for_response($item, $request)
{
// レスポンス用配列の準備。
$data = [];
/**
* フィールドによるフィルタリング
*/
$fields = $this->get_fields_for_response($request);
if( rest_is_field_included('id', $fields) )
{
$data['id'] = $item->id;
}
if( rest_is_field_included('label', $fields) )
{
$data['label'] = $item->label;
}
if( rest_is_field_included('description', $fields) )
{
$data['description'] = $item->description;
}
if( rest_is_field_included('min', $fields) )
{
$data['min'] = $item->min;
}
if( rest_is_field_included('max', $fields) )
{
$data['max'] = $item->max;
}
/**
* 追加フィールドの追加
*/
$data = $this->add_additional_fields_to_object($data, $request);
/**
* コンテキストによるフィルタリング
*/
$context = $request->get_param('context');
$data = $this->filter_response_by_context($data, $context);
/**
* レスポンスの作成、必要であればレスポンスの操作
*/
$response = rest_ensure_response($data);
// レスポンスを返す
return $response;
}
public function create_item_permissions_check($request)
{
return current_user_can('administrator');
}
public function create_item($request)
{
// リクエストからオブジェクトを作成
$item = $this->prepare_item_for_database($request);
if(is_wp_error($item))
{
return $item;
}
// 追加
$id = OsakanaService::addOsakana($item);
// 追加したものをもう一度取得(今回は意味がない)
$osakana = OsakanaService::getOsakana($id);
// 追加フィールドもアップデート
$f = $this->update_additional_fields_for_object($osakana, $request);
if(is_wp_error($f))
{
return $f;
}
// アイテム作成後返信用のアイテムの準備
$data = $this->prepare_item_for_response($osakana, $request);
// レスポンス
$response = rest_ensure_response($data);
$response->set_status(201);
return $response;
}
protected function prepare_item_for_database($request)
{
$item = new stdClass;
if($request->has_param('id'))
{
$item->id = $request->get_param('id');
}
if($request->has_param('label'))
{
$item->label = $request->get_param('label');
}
if($request->has_param('description'))
{
$item->description = $request->get_param('description');
}
if($request->has_param('min'))
{
$item->min = $request->get_param('min');
}
if($request->has_param('max'))
{
$item->max = $request->get_param('max');
}
return $item;
}
public function get_item($request)
{
$id = $request->get_param('id') ?? 0;
if($id)
{
$osakana = OsakanaService::getOsakana($id);
if($osakana)
{
$data = $this->prepare_item_for_response($osakana, $request);
return rest_ensure_response($data);
}
}
return new WP_Error(
'rest_get_osakana_invalid',
'アイテムを取得できませんでした',
['args' => 404]
);
}
public function get_item_permissions_check($request)
{
return true;
}
public function update_item($request)
{
// 対象のアイテム取得
$id = $request->get_param('id') ?? 0;
$osakana = OsakanaService::getOsakana($id);
if(!$osakana)
{
return new WP_Error(
'rest_get_osakana_invalid',
'対象のアイテムを取得できませんでした',
['args' => 404]
);
}
// リクエストからオブジェクトを作成
$item = $this->prepare_item_for_database($request);
if(is_wp_error($item))
{
return $item;
}
// 編集する
OsakanaService::editOsakana((array)$item);
// 編集直後のアイテム
$osakana = OsakanaService::getOsakana($id);
// 追加フィールドの更新
$r = $this->update_additional_fields_for_object($osakana, $request);
if(is_wp_error($r))
{
return $r;
}
// 更新後のアイテムの準備
$data = $this->prepare_item_for_response($osakana, $request);
return rest_ensure_response($data);
}
public function update_item_permissions_check($request)
{
return current_user_can('administrator');
}
public function delete_item($request)
{
// 対象のアイテム取得
$id = $request->get_param('id') ?? 0;
$osakana = OsakanaService::getOsakana($id);
if(!$osakana)
{
return new WP_Error(
'rest_get_osakana_invalid',
'対象のアイテムを取得できませんでした',
['args' => 404]
);
}
// 削除
OsakanaService::deleteOsakana($id);
return rest_ensure_response(true);
}
public function delete_item_permissions_check($request)
{
return current_user_can('administrator');
}
public function get_item_schema()
{
$this->schema = [
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'kurage',
'type' => 'object',
'properties' => [
'id' =>
[
'description' => 'ID',
'type' => 'integer',
],
'label' =>
[
'description' => 'あんたの表示名は?',
'type' => ['string', 'null'],
],
'description' =>
[
'description' => 'てきとーに自己紹介して!',
'type' => ['string', 'null'],
],
'min' =>
[
'description' => '1日のあくびの最小回数は?',
'type' => 'integer',
],
'max' =>
[
'description' => '1日のあくびの最大回数は?',
'type' => 'integer',
]
]
];
return $this->add_additional_fields_schema($this->schema);
}
}
// ルートの登録
add_action('rest_api_init', function(){
register_rest_field(
'kurage',
'ikasan',
[
'get_callback' => fn() => '墨はくどー',
'update_callback' => fn($value) => null,
// ここにschemaを設定していると、add_additional_fields_schema()を呼び出した時にマージされる。
'schema' =>
[
'description' => 'I have 10 legs.',
'type' => 'string'
]
]
);
register_rest_field(
'kurage',
'takosan',
[
'get_callback' => fn() => 'タコ殴りだべー!',
'update_callback' => fn($value) => null,
// ここにschemaを設定していると、add_additional_fields_schema()を呼び出した時にマージされる。
'schema' =>
[
'description' => 'I have 8 legs.',
'type' => 'string'
]
]
);
$controller = new OsakanasController();
$controller->register_routes();
});
ではブラウザから実行してみます。
編集する時
IDが2のアイテムを編集します。
await wp.apiFetch({
path: '/wp/v2/osakanas/2',
method: 'POST',
data: {
label: 'yariika',
description: 'や、やりいか・・・'
}
})
IDが1
と3
のアイテムを削除します。
await wp.apiFetch({ path: '/wp/v2/osakanas/1', method: 'DELETE' })
await wp.apiFetch({ path: '/wp/v2/osakanas/3', method: 'DELETE' })