Codeigniterを使ってREST APIを理解し、実装する方法

以前、「[Codeigniter] APIファーストで見渡しのいいコード設計」を書きましたところ、思ったより反響がありまして、その続編としましてREST APIの理解と設計をご紹介したいと思います。
この記事は、CodeIgniter Advent Calendar 2015の4日目の記事です。

RESTとは

REST APIは、RESTの原則で規定されたAPIを指します。では、RESTとは何でしょうか。詳細を知りたい方はWIkipedia先生なり原典(英語)なりをあたって下さい。ここでは、実装面で必要な情報のみを乗せています。

1.URL/URIとメソッドを組み合わせたリクエスト

Controller的に、処理を一意に考えると、例えば

GET base_url("api/show_list");     //リストの取得
POST base_url("api/register_list");//リストへの登録

という記述をすることが一般的です。しかしながら、RESTでは、URL/URIとメソッドを組み合わせて一意としますので以下のように表記します。同じURLでも、処理はメソッドにより異なるのです。
 

GET base_url("api/list");     //リストの取得
POST base_url("api/list");//リストへの登録

 
PHPで処理を行うときは、URLの可読性を高めるために、よく動詞+名詞によって、処理を規定します。get_listのような書き方ですね。しかしながら、RESTでは、URLはリソースを規定し、そのリソースに対して何を行うかはメソッドで規定するのです(リソースファースト)。
先に出した例だと、listというリソースに対して、GET(取得)するのか、POST(登録)するのか、という話になります。
 
実際問題だと、「じゃあ、listのcountはどう規定するの!」とか考えだすと、1リソース(url/uri)によって全てを規定すると逆に設計疲労を起こすので検討が必要ですが、基本としてはそうなっています。

2.HTTPメソッドに対応した意味を持つ

HTTPメソッドというと、PHPerには、GETとPOSTが馴染みがあると思いますが、あとPUTとDELがあります。REST APIを知るまでは規定されたけど実装されていない形式だと思ってた・・・。
RESTでは、取得はGET、登録はPOST、更新はPUT、削除はDELと規定しており、それに対応した処理を行うということが原則漬けられています。

3.処理の結果は、ステータス・コードで表す

処理が失敗した時に、errorという文字列を返すのではなく、HTTPレスポンスで結果を返します。成功した時には200代のステータスコードを返しますし、失敗した時には300〜500代のステータスコードを返すことになります。
  

PUTとDELは果たして必要か

Codeigniterも3系からPUTとDELが使えるようになったようなのですが、私的には1リソースの処理を4つの処理に分けると、結果としてControllerが分散して管理しづらくなるかなと思っております。もちろん、分散させてController自体の可読性を高めるのは間違いではないのですが、どうしようかなー、とRESTの規定をみていると、そもそもhtmlのformはGETとPOSTしか送れねーよ!な話に紐ついて、「GETとPOSTだけでRESTfulなサービスを作れるか(和訳)」という記事が出ておりました。
 
この記事によると、本質はHTTPメソッドが安全であるか否かであり、データ操作をする「登録」「更新」「削除」にGETを使わなければ、それはひとつのあり方として問題ないとして、GETとPOSTのみのREST設計はありうるという方針を示しています。私もそこは完全に同意なので、PUTとDELを使わなくてもRESTであるといえると思っています。
 

CodeigniterでREST APIを実装する

では、実際にCodeigniterでREST APIを実装してみましょう。ここでは、説明を簡略化するために、modelやlibarayは使わないものとして説明していきます。 
 
まずは、Controllerの構造を規定します。itemというリソースを処理するものと仮定します。

controllers/
└── resources/
    ├── get/
        └── Item.php
    └── update/
        └── Item.php

この地点では、Itemからデータを取得する時、Itemを更新する際はそれぞれ
 

base_url("resources/get/item/****");
base_url("resources/post/item/****");

 
にアクセスすることになり、リソース優先の設計に反しますので、routerによって、getとpostの割当を行います。
 

$route['resources/(:any)/(:any)']['GET']    
    = 'resources/get/$1/$2';
$route['resources/(:any)/(:any)']['POST']
    = 'resources/update/$1/$2';

 
これで準備は完了です。例えば、itemのpref関係を取得する時は
 

class Item extends CI_Controller
{
    function Pref(){
        $this->form_validation->set_data($_GET);
        $this->form_validation->set_rules('l_id', 'l_id', 'is_natural_no_zero|required');
        $this->form_validation->set_rules('c_id', 'c_id', 'is_natural_no_zero|required');
        if($this->form_validation->run()){
                http_response_code($error_code);
                //jsonをecho
        }else{
                http_response_code($error_code);
        }
    }

 
のようにset_dataを使ってGETの値に対するバリデーションを行い、response_codeをつけてjsonを返却してやればいいです。
 
ちなみに、上記では私がいちいちroutingを書きたくないので、Controller単位でroutingして、Controller名とメソッド名は$1/$2で変数化して引き渡してしまっていますが、よりREST化を目指すなら
 

$route['resources/item/pref']['GET']
    = 'resources/item/get_pref';
$route['resources/item/pref']['POST']
    = 'resources/item/update_pref';

 
とか細かく書いていくべきかとも思っています。私は今のところAPIを公開する予定がないので、内部APIファーストにより見渡しのいいコード設計を進めているから前述したような書き方をしている次第です。
 
それでは、また。

お問い合わせ