スマートフォンアプリのバックエンドや、JSフレームワークのバックエンドとして、JSONやXMLを返すAPIをサーバサイドで実装する機会は多いと思います。
今回は、ComposerとCakePHP2.4、FriendsOfCake/crudを使って爆速で実装してみます。
できあがりは、これ
slywalker/cakephp-app-api_sample
まず、プロジェクトのディレクトリに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.js
on
に書き足します。
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のレスポンスを返すようにしましょう。ということでr
outes.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'
],
'DebugKit.Toolbar' => [
'panels' => ['Crud.Crud']
],
'Crud.Crud' => [
'actions' => ['index'],
'listeners' => ['Api']
]
];
}
よしっ!準備は整った!
データの準備
今回はMySQL使うということでよろしくお願いします。サンプルデータがgithubのレポジトリ内のConfig/Schema/ca
keapi.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が返すデータ配列ってモデル名がついてて、なんかいやですよね。そんなときは、ApiTransformation
Listener
を使ってください!
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)にメールアドレスが記載してあります。