アナログCPU:5108843109

ゲームと音楽とプログラミング(酒と女とロックンロールのノリで)

('ω') < イザユケエンジニャー

CodeIgniter入門 #9:webAPIを作ってみる

CodeIgniter入門シリーズ カテゴリーの記事一覧 - アナログCPU:5108843109

今回は、アプリケーションとwebAPIを分離して開発することを想定してサンプルを作ってみます。

今回やること

  • アプリケーションとwebAPIを分離したディレクトリ構造にする
  • webAPIっぽいものを作ってみる
  • アプリケーションのコントローラからwebAPIをコールする
  • アプリケーションの画面フォームからコントローラを呼び出す(間接的にwebAPIをコールする)

ディレクトリ構造

こんな感じにしてみました。
もちろん、/www/index.php と /www/api/v1/index.php の内容も適宜調整します。

  • application
    • web(CodeIgniterのapplication以下そのまま)
    • api
      • v1(CodeIgniterのapplication以下そのまま)
  • system(CodeIgniterのsystem以下そのまま)
  • www

作ってみるAPI

RESTAPIでいってみよう。
参考: REST APIとは? - API設計のポイント

ユーザーテーブル(user)を用意して、それに対して操作するAPIにしようと思います。

  • GET /api/v1/user/ 一覧取得
  • GET /api/v1/user/1/ ID:1を取得
  • POST /api/v1/user/ 登録
  • PUT /api/v1/user/1/ ID:1を変更
  • DELETE /api/v1/user/1/ 削除

userではなくusersにする方が一般的らしいんだけど、今回はこれで。

userテーブルは以下の感じ。(サンプルなのですごい最低限だしパスワードも暗号化しない)

  • id(int / PKでauto_increment)
  • login_id(varchar)
  • password(varchar)

まずはAPIを実装してみる

このURLになるなら、userコントローラを作り、indexの中で処理を分岐させればよいだろうと思う。
ただし、処理の分岐というのは「methodを見分けて適切な関数をコールし分けるだけ」のはずであり、
他のコントローラでも使いまわすはずなので、コアクラスに書くことを考える。

コアクラス

CI_Controllerを継承したMY_Controllerを継承したWebApiControllerを作り、これをコントローラで継承することにする。

<?php
class MY_Controller extends CI_Controller
{
    (省略)
}

class WebApiController extends MY_Controller
{
    public function __construct() { (省略) }

    public function common()
    {
        // methodの判別
        $method = $this->input->method(FALSE);

        // 引数(get,post,streamをマージ)
        $stream = json_decode($this->input->raw_input_stream, true);
        $param_list = array_merge(
            $this->input->get(),
            $this->input->post(),
            !is_array($stream) ? array() : $stream
        );

        // 対応する関数をコール
        if (is_callable(array($this, $method)) && in_array($method, array("get", "post", "put", "delete")))
        {
            echo json_encode($this->$method($param_list));
        }
        else
        {
            echo json_encode(array('msg' => "Undefined API"));
        }
    }
}

特に意味もなく全文貼ったが、このcommonというのは要するに

  • methodを判別して
  • 引数は雑にgetとpostとstreamを全部マージして
  • methodと同名の関数があり、かつそれがget,post,put,deleteのいずれかであればそこに引数を渡す
  • 呼ぶべき関数がなければメッセージを返す

というだけである。

真面目に実装するなら場合によっていろいろ調整が必要かもしれないけど今回はこれで。

ルーティング

config/routes.php

$route['user/(:num)'] = 'user/index/$1';

を追加。
これで、/user/123/ にアクセスすると
userコントローラのindex関数の引数に渡されます。

Userコントローラ
<?php
class User extends WebApiController {

    var $url_param_list = array();

    // コンストラクタ
    public function __construct() { (省略) }

    // API受け口(コアクラスで共通処理を行う)
    public function index($id = null)
    {
        // クエリパラメータ
        $this->url_param_list['id'] = empty($id) ? NULL : $id;

        // 共通処理をコール
        parent::common();
    }

    public function get($param_list)
    {
        $arg_list = array();
        $sql  = "SELECT `id`, `login_id`, `password` ";
        $sql .= "FROM   `user` ";
        if (preg_match("/^[0-9]{1,9}$/", $this->url_param_list['id']))
        {
            $sql .= "WHERE  `id` = ? ";
            $arg_list[] = $this->url_param_list['id'];
        }

        return $this->db->query($sql, $arg_list)->result();
    }

    public function post($param_list)
    {
        $arg_list = array();
        $sql  = "INSERT INTO `user` (`login_id`, `password`) ";
        $sql .= "VALUES (?, ?) ";
        $arg_list[] = $param_list['login_id'];
        $arg_list[] = $param_list['password'];
        $this->db->query($sql, $arg_list);

        return array(
            'id'       => $this->db->insert_id(),
            'login_id' => $param_list['login_id'],
            'password' => $param_list['password'],
        );
    }

    public function put($param_list)
    {
        $arg_list = array();
        $sql  = "UPDATE `user` ";
        $sql .= "SET    `login_id` = ? ";
        $sql .= "      ,`password` = ? ";
        $sql .= "WHERE  `id` = ? ";
        $arg_list[] = $param_list['login_id'];
        $arg_list[] = $param_list['password'];
        $arg_list[] = $this->url_param_list['id'];
        $this->db->query($sql, $arg_list);

        return array(
            'id'       => $this->url_param_list['id'],
            'login_id' => $param_list['login_id'],
            'password' => $param_list['password'],
        );
    }

    public function delete($param_list)
    {
        $arg_list = array();
        $sql  = "DELETE FROM `user` ";
        $sql .= "WHERE       `id` = ? ";
        $arg_list[] = $this->url_param_list['id'];
        $this->db->query($sql, $arg_list);

        return array(
            'id' => $this->url_param_list['id'],
        );
    }
}

また長々と貼っていますが、__construct と index に加え、get, post, put, delete の4つの関数があります。
どこから来てもまずは index で受けることになり、ルーティングで定義されていた引数(ここではID)を確保したのち、あとはさっきのコアクラスのcommonにお任せします。
で、commonからは get, post, put, delete のいずれかにやってきて、必要なクエリを実行したら戻り値を返しておしまい、です。

バリデートなどは無視しているとはいえ、意外にサクッと実装できました。
動作確認は「Advanced REST Client(Chromeアプリ)」などを使えば簡単に行えます。