#CakePHP 爆速でAPIを実装するチュートリアル
スマートフォンアプリのバックエンドや、JSフレームワークのバックエンドとして、JSONやXMLを返すAPIをサーバサイドで実装する機会は多いと思います。
今回は、ComposerとCakePHP2.4、FriendsOfCake/crudを使って爆速で実装してみます。
できあがりは、これ
slywalker/cakephp-app-api_sample
CakePHPのインストール
まず、プロジェクトのディレクトリにcomposer.json
をつくります
composer.json
{ "require": { "pear-cakephp/cakephp": "2.4.*" }, "config": { "vendor-dir": "Vendor/" }, "repositories": [ { "type": "pear", "url": "http://pear.cakephp.org" } ] }
つづいてcomposer.phar
のダウンロードとパッケージのインストール
$ curl -s http://getcomposer.org/installer | php $ php composer.phar install
そして、プロジェクトをBake
$ Vendor/bin/cake bake project $PWD --empty
ブラウザでアクセスすると…
はい。いつもの画面登場!
ただ、このままではCAKE_CORE_INCLUDE_PATH
が絶対パスになっているので、デプロイした際にダメになるので書き換えます。
webroot/index.php, webroot/test.php
define('CAKE_CORE_INCLUDE_PATH', ROOT . DS . APP_DIR . DS . 'Vendor' . DS . 'pear-pear.cakephp.org' . DS . 'CakePHP');
忘れがちなのがConsole/cake.php
Console/cake.php
25c25 < $root = dirname(dirname(dirname(__FILE__))); --- > $app = dirname(dirname(__FILE__)); 29c29 < ini_set('include_path', $root . PATH_SEPARATOR . __CAKE_PATH__ . PATH_SEPARATOR . ini_get('include_path')); --- > ini_set('include_path', $app . $ds . 'Vendor' . $ds . 'pear-pear.cakephp.org' . $ds . 'CakePHP' . PATH_SEPARATOR . ini_get('include_path')); 35c35 < unset($paths, $path, $dispatcher, $root, $ds); --- > unset($paths, $path, $dispatcher, $app, $ds);
最後に、database.php
をbake
$ Console/cake bake db_config
プラグインのインストール
今回の目玉、FriendsOfCake/crud
とcakephp/debug_kit
を入れておきましょう。composer.json
に書き足します。
composer.json
{ "require": { "pear-cakephp/cakephp": "2.4.*", "cakephp/debug_kit": "~2.2", "FriendsOfCake/crud": "3.*" }, "config": { "vendor-dir": "Vendor/" }, "repositories": [ { "type": "pear", "url": "http://pear.cakephp.org" } ] }
Composerでインストールします。
$ php composer.phar update
CakePHPがプラグインを読み込むように設定しときましょう。
Config/bootstrap.php
CakePlugin::loadAll();
Crudプラグインの設定他
さて、Crudプラグインを使えるように設定していきましょう。今回は詳しいとこは省くので、「ん?」と思ったら公式ドキュメントを参照してください。
今回は、.jsonのアクセスでJSONのレスポンス、.xmlのアクセスでXMLのレスポンスを返すようにしましょう。ということでroutes.php
にこれを追加。
Config/routes.php
Router::parseExtensions('json', 'xml');
そして、AppController.phpをゴリゴリ…
Controller/AppController.php
<?php App::uses('Controller', 'Controller'); App::uses('CrudControllerTrait', 'Crud.Lib'); // これ書いといて class AppController extends Controller { use CrudControllerTrait; // トレイト使うよ!なんてモダン public $components = [ 'Session', 'RequestHandler', // これが拡張子で処理をわけてくれるのさ 'Paginator' => [ 'paramType' => 'querystring' // APIっぽくクエリ形式 ], 'DebugKit.Toolbar' => [ 'panels' => ['Crud.Crud'] // Crud用のパネルがあるのさ ], 'Crud.Crud' => [ 'actions' => ['index'], // とりあえずindexのアクションだけ 'listeners' => ['Api'] // ApiListenerを使うよ ] ]; }
よしっ!準備は整った!
データの準備
今回はMySQL使うということでよろしくお願いします。サンプルデータがgithubのレポジトリ内のConfig/Schema/cakeapi.sql.gz
にあるのでインポートしてください。
ちなみに、ちょっと凝ったことをしようと思ったのでデータは東京駅を中心としたランダムの位置情報1万件です。geometry型を使ってます。
Model
さくっと
Model/Geometry.php
<?php App::uses('AppModel', 'Model'); App::uses('Sanitize', 'Utility'); class Geometry extends AppModel { public $virtualFields = [ 'lat' => 'Y(`latlng`)', 'lng' => 'X(`latlng`)' ]; public function conditionCenter($queryParams) { $queryParams = Sanitize::clean($queryParams) + [ 'lat' => null, 'lng' => null ]; if ( !is_numeric($queryParams['lat']) || !is_numeric($queryParams['lng']) ) { return []; } return ["MBRContains( GeomFromText( Concat( 'LineString(', {$queryParams['lng']} + 1, ' ', {$queryParams['lat']} + 1, ',', {$queryParams['lng']} - 1, ' ', {$queryParams['lat']} - 1, ')' ) ), latlng )"]; } }
緯度経度は、バーチャルフィールドを使います。中心からの範囲検索ようにメソッドを用意してます。
Controller
うりゃっと
Controller/Geometries.php
<?php App::uses('AppController', 'Controller'); class GeometriesController extends AppController { public function beforeFilter() { $this->Crud->on('beforePaginate', function(CakeEvent $event) { $model = $event->subject->model; $request = $event->subject->request; $event->subject->paginator->settings += [ 'conditions' => [ $model->conditionCenter($request->query) ] ]; }); parent::beforeFilter(); } }
はい。これだけです…
「は?」と思ったら公式ドキュメントですからね!
レスポンス
ではでは、アクセスしてみましょう。
http://localhost/geometries.json
{ "success": true, "data": [ { "Geometry": { "id": "1", "latlng": null, "lat": "84.001196", "lng": "191.951974" } }, { "Geometry": { "id": "2", "latlng": null, "lat": "51.617372", "lng": "162.921083" } }, .... ] }
おおー!20件表示されてますね。これはPaginateのデフォルトだからですね。
ちなみに、
http://localhost/geometries.json?limit=1
{ "success": true, "data": [ { "Geometry": { "id": "1", "latlng": null, "lat": "84.001196", "lng": "191.951974" } } ] }
おおー!効いてる効いてる。
やっぱり、全体の件数とか、何ページ目とか知りたいですよね。そんなときはApiPaginationListener
を追加するんです。
Controller/AppController.php
<?php class AppController extends Controller { use CrudControllerTrait; public $components = [ 'Crud.Crud' => [ 'actions' => ['index'], 'listeners' => [ 'Api', 'ApiPagination' // これ! ] ] ]; }
さて、どうだ?
http://localhost/geometries.json?page=2&limit=3
{ "success": true, "data": [ { "Geometry": { "id": "4", "latlng": null, "lat": "83.012136", "lng": "165.754295" } }, { "Geometry": { "id": "5", "latlng": null, "lat": "123.59616", "lng": "201.408059" } }, { "Geometry": { "id": "6", "latlng": null, "lat": "80.112906", "lng": "177.030496" } } ], "pagination": { "page_count": 3334, "current_page": 2, "has_next_page": true, "has_prev_page": true, "count": 10000, "limit": 3 } }
へー(×3)。これはAPIの受け手にやさしいですよね。
でも、CakePHPが返すデータ配列ってモデル名がついてて、なんかいやですよね。そんなときは、ApiTransformationListener
を使ってください!
Controller/AppController.php
<?php class AppController extends Controller { use CrudControllerTrait; public $components = [ 'Crud.Crud' => [ 'actions' => ['index'], 'listeners' => [ 'Api', 'ApiPagination', 'ApiTransformation // これ! ] ] ]; }
どうなるかな?
http://localhost/geometries.json?page=2&limit=3
{ "success": true, "data": [ { "id": 4, "latlng": null, "lat": 83.012136, "lng": 165.754295 }, { "id": 5, "latlng": null, "lat": 123.59616, "lng": 201.408059 }, { "id": 6, "latlng": null, "lat": 80.112906, "lng": 177.030496 } ], "pagination": { "page_count": 3334, "current_page": 2, "has_next_page": true, "has_prev_page": true, "count": 10000, "limit": 3 } }
わぁお!もう最高!
あ、肝心の絞込検索やってなかったですね。
http://localhost/geometries.json?lat=35.67832667&lng=139.77044378
{ "success": true, "data": [ { "id": 2583, "latlng": null, "lat": 35.790109, "lng": 140.713021 }, { "id": 5111, "latlng": null, "lat": 35.759589, "lng": 140.428571 }, { "id": 6944, "latlng": null, "lat": 36.627709, "lng": 140.225557 } ], "pagination": { "page_count": 1, "current_page": 1, "has_next_page": false, "has_prev_page": false, "count": 3, "limit": 20 } }
はい!見事に絞れております!
最後に
Crudプラグインは、GETだけじゃなくてPOSTやPUT、UPDATE、DELETEにも対応してるので、夢が広がりまくりングですよ。
と、こんなエントリーが書けるくらいフリーになって暇しているエンジニアがここにいるので、なにかお仕事ください!
CakePHPの実装指導やパフォーマンス改善、実際のコーディングなどなど、お待ちしております。連絡先はslywalker (Yasuo Harada)にメールアドレスが記載してあります。