#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)ã«ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ãŒè¨˜è¼‰ã—ã¦ã‚ã‚Šã¾ã™ã€‚