_一個真正的強者,不是擺平了多少人,而是他能幫助到多少人。 --開源中國源創會分享廣州站 @海洋之心-悟空_
***
##1.11.1 模擬開發:獲取開源中國用戶信息接口
首次使用此接口開發框架時,可以先查看此開發示例。
假設,我們需要為開源中國打造一個平放平臺,其中有一個接口是可以根據用戶ID來獲取用戶的基本信息。
本文,就以模擬獲取開源中國用戶信息接口開發(即:從數據庫獲取用戶的基本信息并以JSON格式返回給客戶端)為例,簡明的接口開發中的流程,以及用到的基本功能和操作,主要包括有:統一入口文件、參數規則配置、接口層/領域層/模型持久層、日志紀錄、數據庫操作、配置讀取、國際化翻譯等。
以給大家一個感觀的認識。
最終接口的調用與返回結果如下:
```javascript
//接口請求格式
http://dev.phalapi.com/demo/?service=User.GetBaseInfo&user_id=帳號ID
//返回結果格式
{
"ret": 200,
"data": {
"code": 0, //狀態碼,0表示正常獲取,1表示用戶不存在
"msg": "",
"info": { //用戶信息
"id": "1", //用戶ID
"name": "dogstar", //帳號
"note": "oschina" //來源
}
},
"msg": ""
}
```
##1.11.2 開發流程
###(1) 統一入口文件
為了更好保護我們的項目代碼,建議將接口的訪問路徑設置在:./PhalApi/Pubic目錄,并且各套接口(如按版本分:v1/v2/v3等等;按不同終端分:ios/android/pc等)各自獨立入口。所以本示例中將./PhalApi/Pubic/demo/index.php下。
參數入口文件的寫法,我們可以快速得到基本的入口文件如下:
```javascript
// $ vim ./Public/demo/index.php
<?php
/**
* Demo 統一入口
*/
require_once dirname(__FILE__) . '/../init.php';
//裝載你的接口
DI()->loader->addDirs('Demo');
/** ---------------- 響應接口請求 ---------------- **/
$server = new PhalApi();
$rs = $server->response();
$rs->output();
```
此外,我們還需要一個公共的初始化文件:
```javascript
//$ vim ./Public/init.php
<?php
/**
* 統一初始化
*/
/** ---------------- 根目錄定義,自動加載 ---------------- **/
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);
/** ---------------- 注冊&初始化服務組件 ---------------- **/
//自動加載
DI()->loader = $loader;
//配置
DI()->config = new PhalApi_Config_File(API_ROOT . '/Config');
//日志紀錄
DI()->logger = new PhalApi_Logger_File(API_ROOT . '/Runtime',
PhalApi_Logger::LOG_LEVEL_DEBUG | PhalApi_Logger::LOG_LEVEL_INFO | PhalApi_Logger::LOG_LEVEL_ERROR);
//數據操作 - 基于NotORM,$_GET['__sql__']可自行改名
DI()->notorm = function() {
$debug = !empty($_GET['__sql__']) ? true : false;
return new PhalApi_DB_NotORM(DI()->config->get('dbs'), $debug);
};
//調試模式,$_GET['__debug__']可自行改名
DI()->debug = !empty($_GET['__debug__']) ? true : DI()->config->get('sys.debug');
//翻譯語言包設定
SL('zh_cn');
```
###(2)TDD測試驅動開發
遵循最佳實踐,我們在編寫代碼前先編寫單元測試。但同時為了減少編寫測試代碼的痛苦,我們可以先定義接口函數簽名,再通過腳本自動生成測試代碼骨架來提高我們的開發效率。
```javascript
//$vim ./Demo/Api/User.php
<?php
class Api_User extends PhalApi_Api
{
public function getBaseInfo()
{
}
}
```
通過腳本生成測試骨架:
```javascript
$ mkdir -p ./Demo/Tests/Api/
$ cd ./Demo/Tests/Api
$ php ../../../PhalApi/build_phpunit_test_tpl.php ../../Api/User.php Api_User ./../test_env.php
```
然后,根據/Public/demo/index.php入口文件,也搭建一個測試環境的入口文件:
```javascript
vim ./Demo/Tests/test_env.php
```
修正一下Api_Examples_User_Test.php里,測試環境test_env.php的包含路徑:
```javascript
//手動調用test_env.php的路徑
require_once dirname(__FILE__) . '/../test_env.php';
```
并且,修改測試,以符合我們通過userId=1獲取基本信息(名字為dogstar,來源為oschina):
```javascript
//$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();
//自己進行初始化
$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']);
}
```
此時,單元測試是預期失敗的:
```javascript
$ 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.
```
###(3)Api接口層
此接口層,主要是負責響應客戶端的請求,調用需要的領域層進行必要的服務功能提供。
###配置參數規則
為了獲取到用戶ID,我們可以在getRules()函數里面定義參數規則,以便框架自動幫我們過濾獲取需要的參數。
> 溫馨提示:
> 接口層的全部類成員函數都應以小寫開頭。
> 但對外,函數首字母不區分大小寫,因為框架會將請求的函數強制轉換成小寫再執行。
>
> 原因在于:
> 1、我們堅持駝峰法的代碼風格;
> 2、對外界我們可以統一使用大寫來提供服務名稱,如:User.Login,這樣更顯專業。
```javascript
//$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參數,名字也為userId,整型,最小值為1,必須。
####接口實現
```javascript
//$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;
}
```
###(4)Domain領域層
領域層主要是關注復雜業務的處理,以及緩存的處理、耗時操作后臺異步處理等,并調用Model持久層獲取需要的數據。因此,是Api與Model層之間的橋梁。
在此示例中,我們只需要簡單地調用Model層獲取用戶的信息即可,再加強一下用戶ID的合法性判斷。
```javascript
//$ 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;
}
}
```
###(5)Model持久層
此一層主要關注數據從持久存儲的獲取,特別是針對數據庫的操作,但不排除其他媒介,如文件、緩存等。
首先,先準備一下我們需要的表和數據:
```javascript
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實現對數據的操作:
```javascript
//$ vim ./Demo/Model/User.php
<?php
class Model_User
{
public function getByUserId($userId)
{
return DI()->notorm->user->select('*')->where('id = ?', $userId)->fetch();
}
}
```
###(6)單元測試通過啦!
在完成上面簡單的開發后,我們即可以實現接口的開發,運行一下剛才的單元測試,完美通過!
```javascript
$ 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)
```
在單元測試的保證下,我們便可以放心大膽地將我們的代碼發布到外網,提供給更多的開發者,和終端用戶使用。
###(7)數據庫配置
因為是單元測試,所以我們配置搭建了新的一個測試環境,特別對于數據庫的配置,如下:
```javascript
//$ vim ./Config/dbs.php
<?php
/**
* examples配置
*/
return array(
/**
* DB數據庫服務器集群
*/
'servers' => array(
'db_demo' => array(
'host' => '192.168.0.104', //數據庫域名
'name' => 'phalapi_test', //數據庫名字
'user' => 'root', //數據庫用戶名
'password' => '123456', //數據庫密碼
'port' => '3306', //數據庫端口
),
),
/**
* 自定義路由表
*/
'tables' => array(
'__default__' => array(
'prefix' => 'tbl_',
'key' => 'id',
'map' => array(
array('db' => 'db_demo'),
),
),
),
);
```
> 溫馨提示:
> 為了方便在單元測試時進行調試,和查看日志,對于全部查詢、執行的SQL語句都會顯示出來,全部的日志改用控制臺輸出。
###(8)最終接口調用
接口調用的鏈接,這時已經相當明了了,即:域名 + 路徑(/demo) + 參數(可從單元測試那直接獲取)
如本示例的是:
```javascript
http://dev.phalapi.com/demo/?service=User.GetBaseInfo&user_id=1
```
返回的結果是:
```javascript
{
"ret": 200,
"data": {
"code": 0,
"msg": "",
"info": {
"id": "1",
"name": "dogstar",
"note": "oschina"
}
},
"msg": ""
}
```
截圖效果:

> 溫馨提示:
> 如果提示日志寫入失敗,請確保./Runtime目錄具有寫入權限,即0777。
##1.11.3 更多簡明的使用
###(1)日志紀錄
當我們訪問一個不存在的用戶時,將會觸發日志紀錄:
```javascript
DI()->logger->debug('user not found', $this->userId);
```
如訪問:
```
http://dev.phalapi.com/demo/?service=User.GetBaseInfo&user_id=2
```
然后,可以在Runtime下看到按天分目錄的日志:
```javascript
$ tailf ./Runtime/log/201501/20150128.log
2015-01-28 00:37:34|DEBUG|user not found|2
```
> 溫馨提示:
> 外網環境上,請把Runtime目錄軟鏈到磁盤空間很大的路徑。
###(2)國際化翻譯
當需要翻譯時,可以使用人性化的函數T(),如:
```javascript
$rs['msg'] = T('user not exists');
```
對應地需要補充翻譯的內容:
```javascript
//$ vim ./Language/zh_cn/common.php
'user not exists' => '用戶不存在',
```
還是以上面的用戶不存在為例,看下運行的截圖效果:

###(3)配置讀取
配置的讀取,使用方便,直接通過以下方式便可以獲取,以點號分割:
```javascript
DI()->config->get('dbs')
```
第一段,必須為文件名,后面的為用點號相連的數組下標,不限級。
###(4)[酷!]接口參數在線查詢
為了方便客戶端實時查看最新的接口參數,這里提供了一個快速的在線工具:
```javascript
http://dev.phalapi.com/demo/checkApiParams.php?service=User.GetBaseInfo
```
用瀏覽器打開后,即可以看到最新的接口參數說明, 不需要后臺接口開發人員編寫文檔維護,直接從代碼中生成參數報表:

##1.11.4 Demo相關代碼文件
從上面可以得到,此示例相關的代碼如下:
```javascript
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
```
- 歡迎使用PhalApi!
- 接口,從簡單開始!
- [1.1]-下載與安裝
- [1.2]-創建一個自己的項目
- [1.3]-在線體驗
- [1.4]-文檔、幫助和官網
- [1.10]-對PhalApi框架的抉擇
- [1.11]-快速入門(backup)
- [1.12]-參數規則:接口參數規則配置
- [1.13]-統一的接口請求方式:_sevice=XXX.XXX
- [1.14]-統一的返回格式和結構:ret-data-msg
- [1.15]-數據庫操作:基于NotORM的使用及優化
- [1.16]-配置讀取:內外網環境配置的完美切換
- [1.17]-日記紀錄:簡化版的日記接口
- [1.18]-快速函數:人性化的關懷
- [1.19]-DI服務速查:各資源服務一覽表
- [1.20]-DB操作:數據庫基本操作速查
- [1.21]-類的自動加載:遵循PEAR包的命名規范
- [1.22]-簽名驗證:自定義簽名規則
- [1.23]-請求和響應:GET和POST兩者皆可得及超越JSON格式返回
- [1.24]-緩存策略:更靈活地可配置化的多級緩存
- [1.25]-國際化翻譯:為走向國際化提前做好翻譯準備
- [1.26]-數據安全:數據對稱加密方案
- [1.27]-精益開發:更富表現力的Model層和重量級數據獲取的應對方案
- [1.28]-COOKIE:對COOKIE原生態的支持及記憶加密升級版
- [1.29]-開放與封閉:多入口和統一初始化
- [1.30]-保持的力量:接口開發最佳實踐
- [1.31]-新型計劃任務:以接口形式實現的計劃任務
- [2.11]-核心思想:DI依賴注入-讓資源更可控
- [2.12]-海量數據:可配置的分庫分表
- [2.13]-接口調試:在線SQL語句查看與性能優化
- [2.14]-測試驅動開發:意圖導向編程下的接口開發
- [2.15]-演進:新型計劃任務續篇
- [2.16]-領域驅動設計:應對復雜領域業務的Domain層
- [2.17]-微服務:Api接口服務層
- [2.18]-定制化:資源服務的再實現
- [2.19]-擴展庫:可重用的擴展類庫
- [2.20]-約定編程:架構明顯的編程風格
- [2.21]-服務器統一部署方案簡明版:CentOs---Nginx---php-fpm---MySql-[--Memcached]
- [2.22]-更多工具:精益項目和團隊建設
- [3.1]-擴展類庫:微信開發
- [3.2]-擴展類庫:代理模式下phprpc協議的輕松支持
- [3.3]-擴展類庫:基于PHPMailer的郵件發送
- [3.4]-擴展類庫:優酷開放平臺接口調用
- [3.5]-擴展類庫:七牛云存儲接口調用
- [3.6]-擴展類庫:新型計劃任務
- [3.8]-擴展類庫:用戶、會話和第三方登錄集成
- [3.9]-擴展類庫:swoole支持下的長鏈接和異步任務實現
- [3.11]-擴展類庫:基于FastRoute的快速路由
- [4.2]-開發實戰2:模擬優酷開放平臺接口項目開發
- [4.3]-開發實戰3:一個簡單的小型項目開發(奔跑吧兄弟投票活動)
- [5.1]-架構與思想:PhalApi核心設計和思想解讀
- [5.2]-雜談:扯一些PhalApi的前世和今生
- [5.3]-框架總結:術語表和PHP開發建議
- [5.4]-許可
- [5.5]-聯系和加入我們
- [5.6]-更新日記
- [5.8]-致框架貢獻者:加入PhalApi開源指南
- [6.1]-基于接口查詢語言的SDK包
- [6.2]-SDK包(JAVA版)
- [6.3]-SDK包(PHP版)
- [6.4]-SDK包(Objective-C版)
- [6.5]-SDK包(javascript版)
- [6.6]-SDK包(Ruby版)
- [8.1]-PhalApi視頻教程
- 附錄1:接口文檔參考模板