一個真正的強(qiáng)者,不是擺平了多少人,而是他能幫助到多少人。 --開源中國源創(chuàng)會分享廣州站 @海洋之心-悟空
首次使用此接口開發(fā)框架時,可以先查看此開發(fā)示例。
假設(shè),我們需要為開源中國打造一個平放平臺,其中有一個接口是可以根據(jù)用戶ID來獲取用戶的基本信息。
本文,就以模擬獲取開源中國用戶信息接口開發(fā)(即:從數(shù)據(jù)庫獲取用戶的基本信息并以JSON格式返回給客戶端)為例,簡明的接口開發(fā)中的流程,以及用到的基本功能和操作,主要包括有:統(tǒng)一入口文件、參數(shù)規(guī)則配置、接口層/領(lǐng)域?qū)?模型持久層、日志紀(jì)錄、數(shù)據(jù)庫操作、配置讀取、國際化翻譯等。
以給大家一個感觀的認(rèn)識。
最終接口的調(diào)用與返回結(jié)果如下:
//接口請求格式
http://dev.phalapi.com/demo/?service=User.GetBaseInfo&user_id=帳號ID
//返回結(jié)果格式
{
"ret": 200,
"data": {
"code": 0, //狀態(tài)碼,0表示正常獲取,1表示用戶不存在
"msg": "",
"info": { //用戶信息
"id": "1", //用戶ID
"name": "dogstar", //帳號
"note": "oschina" //來源
}
},
"msg": ""
}
為了更好保護(hù)我們的項目代碼,建議將接口的訪問路徑設(shè)置在:./PhalApi/Pubic目錄,并且各套接口(如按版本分:v1/v2/v3等等;按不同終端分:ios/android/pc等)各自獨(dú)立入口。所以本示例中將./PhalApi/Pubic/demo/index.php下。
參數(shù)入口文件的寫法,我們可以快速得到基本的入口文件如下:
// $ vim ./Public/demo/index.php
<?php
/**
* Demo 統(tǒng)一入口
*/
require_once dirname(__FILE__) . '/../init.php';
//裝載你的接口
DI()->loader->addDirs('Demo');
/** ---------------- 響應(yīng)接口請求 ---------------- **/
$server = new PhalApi();
$rs = $server->response();
$rs->output();
此外,我們還需要一個公共的初始化文件:
//$ vim ./Public/init.php
<?php
/**
* 統(tǒng)一初始化
*/
/** ---------------- 根目錄定義,自動加載 ---------------- **/
date_default_timezone_set('Asia/Shanghai');
defined('API_ROOT') || define('API_ROOT', dirname(__FILE__) . '/..');
require_once API_ROOT . '/PhalApi/PhalApi.php';
$loader = new PhalApi_Loader(API_ROOT);
/** ---------------- 注冊&初始化服務(wù)組件 ---------------- **/
//自動加載
DI()->loader = $loader;
//配置
DI()->config = new PhalApi_Config_File(API_ROOT . '/Config');
//日志紀(jì)錄
DI()->logger = new PhalApi_Logger_File(API_ROOT . '/Runtime',
PhalApi_Logger::LOG_LEVEL_DEBUG | PhalApi_Logger::LOG_LEVEL_INFO | PhalApi_Logger::LOG_LEVEL_ERROR);
//數(shù)據(jù)操作 - 基于NotORM,$_GET['__sql__']可自行改名
DI()->notorm = function() {
$debug = !empty($_GET['__sql__']) ? true : false;
return new PhalApi_DB_NotORM(DI()->config->get('dbs'), $debug);
};
//調(diào)試模式,$_GET['__debug__']可自行改名
DI()->debug = !empty($_GET['__debug__']) ? true : DI()->config->get('sys.debug');
//翻譯語言包設(shè)定
SL('zh_cn');
遵循最佳實(shí)踐,我們在編寫代碼前先編寫單元測試。但同時為了減少編寫測試代碼的痛苦,我們可以先定義接口函數(shù)簽名,再通過腳本自動生成測試代碼骨架來提高我們的開發(fā)效率。
//$vim ./Demo/Api/User.php
<?php
class Api_User extends PhalApi_Api
{
public function getBaseInfo()
{
}
}
通過腳本生成測試骨架:
$ mkdir -p ./Demo/Tests/Api/
$ cd ./Demo/Tests/Api
$ php ../../../PhalApi/build_phpunit_test_tpl.php ../../Api/User.php Api_User ./../test_env.php
然后,根據(jù)/Public/demo/index.php入口文件,也搭建一個測試環(huán)境的入口文件:
vim ./Demo/Tests/test_env.php
修正一下Api_Examples_User_Test.php里,測試環(huán)境test_env.php的包含路徑:
//手動調(diào)用test_env.php的路徑
require_once dirname(__FILE__) . '/../test_env.php';
并且,修改測試,以符合我們通過userId=1獲取基本信息(名字為dogstar,來源為oschina):
//$vim ./Demo/Tests/Api/Api_User_Test.php
/**
* @group testGetBaseInfo
*/
public function testGetBaseInfo()
{
$str = 'service=User.GetBaseInfo&user_id=1';
parse_str($str, $params);
DI()->request = new PhalApi_Request($params);
$api = new Api_User();
//自己進(jìn)行初始化
$api->init();
$rs = $api->getBaseInfo();
$this->assertNotEmpty($rs);
$this->assertArrayHasKey('code', $rs);
$this->assertArrayHasKey('msg', $rs);
$this->assertArrayHasKey('info', $rs);
$this->assertEquals(0, $rs['code']);
$this->assertEquals('dogstar', $rs['info']['name']);
$this->assertEquals('oschina', $rs['info']['note']);
}
此時,單元測試是預(yù)期失敗的:
$ phpunit ./Api_User_Test.php
PHPUnit 4.3.4 by Sebastian Bergmann.
F
Time: 3 ms, Memory: 5.25Mb
There was 1 failure:
1) PhpUnderControl_ApiUser_Test::testGetBaseInfo
Failed asserting that a NULL is not empty.
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
此接口層,主要是負(fù)責(zé)響應(yīng)客戶端的請求,調(diào)用需要的領(lǐng)域?qū)舆M(jìn)行必要的服務(wù)功能提供。
為了獲取到用戶ID,我們可以在getRules()函數(shù)里面定義參數(shù)規(guī)則,以便框架自動幫我們過濾獲取需要的參數(shù)。
溫馨提示:
接口層的全部類成員函數(shù)都應(yīng)以小寫開頭。
但對外,函數(shù)首字母不區(qū)分大小寫,因?yàn)榭蚣軙⒄埱蟮暮瘮?shù)強(qiáng)制轉(zhuǎn)換成小寫再執(zhí)行。原因在于:
1、我們堅持駝峰法的代碼風(fēng)格;
2、對外界我們可以統(tǒng)一使用大寫來提供服務(wù)名稱,如:User.Login,這樣更顯專業(yè)。
//$vim ./Demo/Api/User.php
<?php
class Api_User extends PhalApi_Api
{
public function getRules()
{
return array(
'getBaseInfo' => array(
'userId' => array('name' => 'userId', 'type' => 'int', 'min' => 1, 'require' => true),
),
);
}
//...
如上,我們就定義了getBaseInfo接口中的userId參數(shù),名字也為userId,整型,最小值為1,必須。
//$vim ./Demo/Api/User.php
public function getBaseInfo()
{
$rs = array('code' => 0, 'msg' => '', 'info' => array());
$domain = new Domain_User();
$info = $domain->getBaseInfo($this->userId);
if (empty($info)) {
DI()->logger->debug('user not found', $this->userId);
$rs['code'] = 1;
$rs['msg'] = T('user not exists');
return $rs;
}
$rs['info'] = $info;
return $rs;
}
領(lǐng)域?qū)又饕顷P(guān)注復(fù)雜業(yè)務(wù)的處理,以及緩存的處理、耗時操作后臺異步處理等,并調(diào)用Model持久層獲取需要的數(shù)據(jù)。因此,是Api與Model層之間的橋梁。
在此示例中,我們只需要簡單地調(diào)用Model層獲取用戶的信息即可,再加強(qiáng)一下用戶ID的合法性判斷。
//$ vim ./Demo/Domain/User.php
<?php
class Domain_User
{
public function getBaseInfo($userId)
{
$rs = array();
$userId = intval($userId);
if ($userId <= 0) {
return $rs;
}
$model = new Model_User();
$rs = $model->getByUserId($userId);
return $rs;
}
}
此一層主要關(guān)注數(shù)據(jù)從持久存儲的獲取,特別是針對數(shù)據(jù)庫的操作,但不排除其他媒介,如文件、緩存等。
首先,先準(zhǔn)備一下我們需要的表和數(shù)據(jù):
CREATE TABLE `phalapi_test`.`tbl_user` (
`id` INT NOT NULL,
`name` VARCHAR(45) NULL,
`note` VARCHAR(45) NULL,
PRIMARY KEY (`id`));
INSERT INTO `phalapi_test`.`tbl_user` (`id`, `name`, `note`) VALUES ('1', 'dogstar', 'oschina');
然后,編寫需要的Model代碼,即利用NotORm實(shí)現(xiàn)對數(shù)據(jù)的操作:
//$ vim ./Demo/Model/User.php
<?php
class Model_User
{
public function getByUserId($userId)
{
return DI()->notorm->user->select('*')->where('id = ?', $userId)->fetch();
}
}
在完成上面簡單的開發(fā)后,我們即可以實(shí)現(xiàn)接口的開發(fā),運(yùn)行一下剛才的單元測試,完美通過!
$ phpunit ./Api_User_Test.php
PHPUnit 4.3.4 by Sebastian Bergmann.
SELECT * FROM tbl_user WHERE (id = ?); -- 1
.SELECT * FROM tbl_user WHERE (id = ?); -- 1<br />
Time: 34 ms, Memory: 6.50Mb
OK (2 tests, 7 assertions)
在單元測試的保證下,我們便可以放心大膽地將我們的代碼發(fā)布到外網(wǎng),提供給更多的開發(fā)者,和終端用戶使用。
因?yàn)槭菃卧獪y試,所以我們配置搭建了新的一個測試環(huán)境,特別對于數(shù)據(jù)庫的配置,如下:
//$ vim ./Config/dbs.php
<?php
/**
* examples配置
*/
return array(
/**
* DB數(shù)據(jù)庫服務(wù)器集群
*/
'servers' => array(
'db_demo' => array(
'host' => '192.168.0.104', //數(shù)據(jù)庫域名
'name' => 'phalapi_test', //數(shù)據(jù)庫名字
'user' => 'root', //數(shù)據(jù)庫用戶名
'password' => '123456', //數(shù)據(jù)庫密碼
'port' => '3306', //數(shù)據(jù)庫端口
),
),
/**
* 自定義路由表
*/
'tables' => array(
'__default__' => array(
'prefix' => 'tbl_',
'key' => 'id',
'map' => array(
array('db' => 'db_demo'),
),
),
),
);
溫馨提示:
為了方便在單元測試時進(jìn)行調(diào)試,和查看日志,對于全部查詢、執(zhí)行的SQL語句都會顯示出來,全部的日志改用控制臺輸出。
接口調(diào)用的鏈接,這時已經(jīng)相當(dāng)明了了,即:域名 + 路徑(/demo) + 參數(shù)(可從單元測試那直接獲取)
如本示例的是:
http://dev.phalapi.com/demo/?service=User.GetBaseInfo&user_id=1
返回的結(jié)果是:
{
"ret": 200,
"data": {
"code": 0,
"msg": "",
"info": {
"id": "1",
"name": "dogstar",
"note": "oschina"
}
},
"msg": ""
}
截圖效果:
溫馨提示:
如果提示日志寫入失敗,請確保./Runtime目錄具有寫入權(quán)限,即0777。
當(dāng)我們訪問一個不存在的用戶時,將會觸發(fā)日志紀(jì)錄:
DI()->logger->debug('user not found', $this->userId);
如訪問:
http://dev.phalapi.com/demo/?service=User.GetBaseInfo&user_id=2
然后,可以在Runtime下看到按天分目錄的日志:
$ tailf ./Runtime/log/201501/20150128.log
2015-01-28 00:37:34|DEBUG|user not found|2
溫馨提示:
外網(wǎng)環(huán)境上,請把Runtime目錄軟鏈到磁盤空間很大的路徑。
當(dāng)需要翻譯時,可以使用人性化的函數(shù)T(),如:
$rs['msg'] = T('user not exists');
對應(yīng)地需要補(bǔ)充翻譯的內(nèi)容:
//$ vim ./Language/zh_cn/common.php
'user not exists' => '用戶不存在',
還是以上面的用戶不存在為例,看下運(yùn)行的截圖效果:
配置的讀取,使用方便,直接通過以下方式便可以獲取,以點(diǎn)號分割:
DI()->config->get('dbs')
第一段,必須為文件名,后面的為用點(diǎn)號相連的數(shù)組下標(biāo),不限級。
為了方便客戶端實(shí)時查看最新的接口參數(shù),這里提供了一個快速的在線工具:
http://demo.phalapi.net/checkApiParams.php?service=User.GetBaseInfo
用瀏覽器打開后,即可以看到最新的接口參數(shù)說明, 不需要后臺接口開發(fā)人員編寫文檔維護(hù),直接從代碼中生成參數(shù)報表:
從上面可以得到,此示例相關(guān)的代碼如下:
Demo$ tree
.
├── Api
│ ├── Default.php
│ └── User.php
├── Domain
│ └── User.php
├── Model
│ └── User.php
└── Tests
├── Api
│ ├── Api_Default_Test.php
│ └── Api_User_Test.php
└── test_env.php
更多建議: