PHPでRESTなAPIを作ることになり、codeigniterはパフォーマンスが良いという話をよく聞くので試してみた。
ただし、デフォルトではrestに対応していないようで、ググっても皆さん独自で対応されている模様。
今回は hanischit/codeigniter-restserver を使ってrest対応した。
動作環境
- CentOS6.7
- nginx
- MySQL5.6
- php7.0
vagrantとansibleを使って構築した
⇒ github に置いてあります
- AmazonLinux使うかもしれないのでCentOSは6系にしておいた
- APIバージョン毎にディレクトリを分ける想定だが、
その辺りの作り込みは適当
⇒ 中途半端過ぎたので一旦やめた
codeigniter3のインストール
composer.json
プロジェクトのディレクトリ(APPPATH)直下に composer.json を作成。
codeigniter本体とrest拡張、ローカライズ言語ファイルも追加。
{
"repositories": [
{
"type":"package",
"package": {
"name": "bcit-ci/codeigniter3-translations",
"version":"dev",
"source": {
"url": "https://github.com/bcit-ci/codeigniter3-translations.git",
"type": "git",
"reference":"develop"
}
}
}
],
"require": {
"php": ">=5.2.4",
"codeigniter/framework": ">=3.0",
"hanischit/codeigniter-restserver": "^1.0",
"bcit-ci/codeigniter3-translations": "dev"
},
"require-dev": {
"mikey179/vfsStream": "1.1.*"
}
}
composer本体のインストールと実行
# curl -sS https://getcomposer.org/installer | php
# php composer.phar install
codeigniterの構成
ディレクトリ構成を変更
-
APPPATH/public_html
ディレクトリを作成して、APPPATH/vendor/codeigniter/index.php
をコピー- 確認用のphpinfoとかもここに置く
-
APPPATH/vendor/codeigniter/application
ディレクトリをAPPPATH
にコピー- ここにアプリを作成する
- 環境依存ファイルを準備(とりあえずDBのみ)
- APPPATH/application/config/{development|testing|production}/database.php
変更したディレクトリ構成に合わせて設定変更
APPPATH/public_html/index.php
- パスの変更
$application_folder = '../application';
$system_path = '../vendor/codeigniter/framework/system';
APPPATH/application/config/config.php
- autoloadの設定を修正
//$config['composer_autoload'] = FALSE;
$config['composer_autoload'] = TRUE;
$config['composer_autoload'] = FCPATH . '/vendor/autoload.php';
- indexの設定を修正
//$config['index_page'] = 'index.php';
$config['index_page'] = '';
日本語ファイルを取得
bcit-ci/codeigniter3-translationsを使用。
-
vendor/bctit-ci/codeigniter3-translations/language/japanese
をAPPPATH/application/language
にコピー -
APPPATH/application/config/config.php の設定変更
//$config['language'] = 'english';
$config['language'] = 'japanese';
※言語ファイルはAPPPATH/language
> vendor/codeigniter/framework/system/language
の優先順位で参照している模様
REST対応
下記のファイルをコピーするだけ
-
vendor/hanischit/codeigniter-restserver/libraries/REST_Controller.php
⇒ application/libraries/REST_Controller.php -
vendor/hanischit/codeigniter-restserver/libraries/Format.php
⇒ application/libraries/Format.php -
vendor/hanischit/codeigniter-restserver/config/rest.php
⇒ application/config/rest.php -
vendor/hanischit/codeigniter-restserver/language/english/rest_controller_lang.php
⇒ application/language/english/rest_controller_lang.php
APIサンプル
GET /users
#routeを追加
$route['users'] = 'users/list';
#メソッドを厳密にしたい場合は下記
$route['users']['get'] = 'users/list';
<?php
require APPPATH . '/libraries/REST_Controller.php';
class Users extends REST_Controller {
function __construct()
{
// Construct the parent class
parent::__construct();
}
public function list_get()
{
// 1. クエリパラメータのバリデーション
// (array)$this->query()をバリデーションする
// 2. データ取得した事にする
$users = [
['id' => 1, 'name' => 'John', 'email' => 'john@example.com', 'fact' => 'Loves sax'],
['id' => 2, 'name' => 'Jim', 'email' => 'jim@example.com', 'fact' => 'Loves guitar'],
['id' => 3, 'name' => 'Miles', 'email' => 'miles@example.com', 'fact' => 'Loves trumpet'],
];
if (!empty($users))
{
$this->set_response($users, REST_Controller::HTTP_OK);
}
else
{
$this->set_response([
'status' => FALSE,
'message' => 'No users were found',
], REST_Controller::HTTP_NOT_FOUND);
}
}
REST_Controllerを継承したコントローラを書くだけでjsonで返ってくる。
http://localhost/users?format=xml
とすればXMLで取得できるが、http://localhost/users.xml
だと正常にルーティングされなくなってしまう模様。
(ドキュメント見ると出来るっぽいけど。。ルーティングに正規表現が必要??)
本来のサンプルでは「各エンドポイントに対応するクラスは、厳密にはコントローラではない」という考え方なのか、controller/api
というディレクトリの下にコントローラを作成している。
とはいえ、ブラウザでアクセスするURIと混在させないので、その辺は気にしなくていいと思う。
PUT /users/{id}
リクエストボディは POST
の時と同じ階層にした方がバリデーションしやすいと思う
[
{
"name": "Elvin",
"email": "elvin@example.com",
"fact": "Loves drums"
}
]
$route['users/(:num)'] = 'users/detail/$1';
public function detail_put(Int $id)
{
// 1. クエリパラメータのバリデーション
// $this->query()をバリデーションする
// 2. リクエストボディのバリデーション
$request_body = json_decode($this->put()[0], true)
// $request_bodyをバリデーションする
// 3. データ更新したことにする
$updated_user = [
'id' => $id,
'name' => $request_body[0]['name'],
'email' => $request_body[0]['email'],
'fact' => $request_body[0]['fact']
];
if (!empty($updated_user))
{
$this->set_response($updated_user, REST_Controller::HTTP_OK);
}
else
{
$this->set_response([
'status' => FALSE,
'message' => 'error',
], REST_Controller::HTTP_INTERNAL_SERVER_ERROR);
}
}
$this->put()
が文字列のまま渡ってきた。。。
とりあえず動いた
まぁだいたい動きそう。
後は細かいところがどうかという感じで、ダメならFuelかな。。
作ったサンプルはgithub に置いてあります