_“播下一種思想,收獲一種行為;播下一種行為,收獲一種習慣;播下一種習慣,收獲一種性格;播下一種性格,收獲一種命運。” --《成君憶:水煮三國》_
##1.12.1 參數解析
參數,對于接口來說,是非常重要的輸入。對于外部調用來說,同等重要。
因此,對于參數這塊,我們是希望能夠既減輕后臺開發對接口參數獲取、判斷、驗證、文檔編寫的痛苦;又便于客戶端方便的、自由的調用;既利已又利他。
由此,我們引入了 **參數解析** 這一概念,即:通過配置參數的規則,即可自動實現參數的獲取和驗證。
##1.12.2 參數解析的配置規則
熟悉Yii的同學,對于以下的規則配置應該倍感親切,但是不熟悉的同學也可以同樣快速上手。因為,你會慢慢發現,這樣的規則很符合我們PHP開發的規范,如果沒有,我們繼續努力改進。
格式如下:
```javascript
array(
'參數名' => array('name' => '接口參數名稱', 'type' => '類型', 'default' => '默認值', ...),
... ...
)
```
##1.12.3 示例
###(1)簡單的示例
假設這樣的業務場景,我們需要提供一個用戶登錄的接口,其中需要用戶名和密碼,因此:
```javascript
<?php
class Api_User extends PhalApi_Api
{
public function getRules()
{
return array(
'login' => array(
'username' => array('name' => 'username'),
'password' => array('name' => 'password'),
),
);
}
public function login()
{
return array('username' => $this->username, 'password' => $this->password);
}
}
```
當我們這樣調用接口時:
```
/?service=User.Login&username=test&password=123456
```
就可以獲取到需要的參數:
```javascript
{"ret":0,"data":{"username":"test","password":"123456"},"msg":""}
```
從中,可以很容易理解:參數規則需要統一配置在接口實現類里面的 **getRules()** 函數,隨后即可以通過類成員屬性方式獲取,如: **$this->username** 。
###(2)更完善的示例
很多時候我們都會對用戶名和密碼作一些驗證,如是否必須、長度、最值,以及默認值等。
繼續上面的業務場景,我們登錄下用戶名和密碼必須,且密碼長度至少為6個字符,則可以調整參數規則:
```javascript
'login' => array(
'username' => array('name' => 'username', 'require' => true),
'password' => array('name' => 'password', 'require' => true, 'min' => 6),
),
```
嘗試一下非法的參數請求,如無任何參數的情況下,訪問/?service=User.Login,返回:
```javascript
{"ret":400,"data":[],"msg":"Illegal Param: wrong param: username"}
```
再嘗試一下密碼長不對的情況,訪問/?service=User.Login&username=test&password=123,返回:
```javascript
{"ret":400,"data":[],"msg":"Illegal Param: password.len should >= 6, but now password.len = 3"}
```
##1.12.4 三級參數
###(1)系統參數
已被系統固定占有的參數,目前只有一個,即:service,為需要調用的服務,類型為字符串,格式為:XXX.XXX,首字母不區分大小寫,建議統一以大寫開頭。
以下是一些示例:
```javascript
#推薦寫法
/?service=User.GetBaseInfo
#正確寫法(開頭小寫)
/?service=user.getBaseInfo
#正確寫法(方法名小寫,但類名只能開頭小寫,否則會導致linux系統下文件加載失敗)
/?service=user.getbaseinfo
#錯誤寫法(缺少方法名)
/?service=User
#錯誤寫法(缺少點號分割)
/?service=UserGetBaseInfo
#錯誤寫法(默認只支持點號分割)
/?service=User|GetBaseInfo
```
###(2)應用參數
應用參數是指在一個項目中,全部接口都需要的參數,或者通用的參數規則。假如我們的項目中全部需要簽名sign參數,且必須;以及非必須的版本號,則可以在./Config/app.php中的apiCommonRules配置:
```javascript
//$vim ./Config/app.php
<?php
return array(
/**
* 應用接口層的統一參數
*/
'apiCommonRules' => array(
//簽名
'sign' => array(
'name' => 'sign', 'require' => true,
),
//客戶端App版本號,如:1.0.1
'version' => array(
'name' => 'version', 'default' => '',
),
),
... ...
```
###(3)接口參數
接口參數即為上面在各個接口子類中配置的規則,為特定接口所持有。同時,為了方便同一套接口的規則重用,可以使用下標為 '*' 表示是本接口通用規則,如我們為了加強安全性,為全部的用戶接口操作都加上4位的驗證碼:
```javascript
public function getRules()
{
return array(
'*' => array(
'code' => array('name' => 'code', 'require' => true, 'min' => 4, 'max' => 4),
),
'login' => array(
'username' => array('name' => 'username', 'require' => true),
'password' => array('name' => 'password', 'require' => true, 'min' => 6),
),
);
}
```
在完成對上面的應用參數規則、接口通用規則和指定規則的參數進行配置后,對用戶登錄的接口進行請求時就需要這樣訪問:
```javascript
/?service=User.login&sign=77f81c17d512302383e5f26b99dae4d9&username=test&password=123456&code=abcd
```
> 溫馨提示:
> 在Api類里面配置規則時,下標不區分大小寫。因為框架會自動將請求的函數名和全部的規則下標轉換成小寫進行匹配。
這里,再小結一下,接口參數可以分為兩種: **通用接口參數** 和 **指定接口參數** 。前者用 * 號下標表示,后者則用函數名作為下標表示。
###(4)多個參數規則的優先級
當同一個參數規則分別在應用參數、接口通用參數及特定接口參數出現時,后面的規則會覆蓋前面的,即具體化的規則會替換通用的規則,以保證接口在特定場合的定制性。
簡而言之,多個參數規則的優先級從高到下,分別是(正如你想到的那樣):
+ 1、指定接口參數
+ 2、通用接口參數
+ 3、應用參數
+ 4、系統參數(通常忽略,因為只有service)
##1.12.5 在線接口參數查詢工具
為了便于理解上面全部的參數規則,對于具體接口調用的要求,這里可以使用在線接口參數查詢工具在瀏覽器訪問查看:
```javascript
/demo/checkApiParams.php?service=User.Login
```
可以看到:

此工具同時也可以方便客戶端實時查看接口文檔時,進行輔助的接口規則說明。
###自描述數據
這里值得一提的是,我們這里所定義的參數規則實際上也是自描述數據。即配置的代碼真實同步反映了參數的相關屬性。
##1.12.6 參數傳遞的方式
系統下GET和POST皆可,但是推薦:
+ 1、service參數以GET方式傳遞,接口統一以/?service=XXX.XXX鏈接請求,便于交流,更重要的是當接口發生問題時,可以快速在服務器上通過nginx日志定位問題;
+ 2、其他參數以POST方式傳遞,特別對于敏感數據,如密碼,以相對保護數據安全;
+ 3、在編寫文檔,或者進行調試時,可以全部臨時使用GET方式,如本文檔的寫法,同時在瀏覽器時也可以使用GET;
+ 4、如果需要對數據包進行加密或者壓縮、自定義參數格式,可以重載PhalApi_Request::genData(),然后再繼續使用參數規則解析;
##1.12.7 參數規則
類型type|參數名稱 name|是否必須require|默認值default|最小值&最大值min&max|更多
---|---|---|---|---|---
字符串|string|true/false,默認false|應為字符串|可選|regex下標為正則匹配的規則;format下標可用于定義字符編碼的類型,如utf8、gbk,gb2312
整數|int|true/false,默認false|應為整數|可選|---
浮點數|float|true/false,默認false|應為浮點數|可選|---
布爾值|boolean|true/false,默認false|true/false|---|以下值會轉換為true: ok, true, success, on, yes, 1
時間戳/日期|date|true/false,默認false|會按格式轉換|可選,僅當為timestamp時才判斷|格式:format 為timestamp時會將字符串的日期轉換
數組|array|true/false,默認false|為非數組會自動轉換/解析成數組|可選,判斷數組元素個數|格式:format 為explode時,會根據separator將字符串分割成數組, 為json時,會json解析
枚舉|enum|true/false,默認false|應為range中的某個元素|---|必須,range,以數組指定枚舉的范圍
文件|file|true/false,默認false|數組類型|min和max表示文件大小范圍|range下標表示允許上傳的文件類型,ext表示需要過濾的文件擴展名
回調|callable|true/false,默認false|---|callback設置回調函數,params為回調函數的第三個參數,第一個為參數值,第二個為所配置的規則
> 溫馨提示:
> 全部的參數規則,都可以配置desc下標,對應在線接口文檔的”說明“部分。
> 如: array('name' => 'username', 'desc' => '用戶名')
下面是對各類型的示例說明。
###(1)字符串 string
當一個參數規則 未指定類型時,默認為string。一個完整的寫法可以為:
```javascript
array('name' => 'username', 'type' => 'string', 'require' => true, 'default' => 'nobody', 'min' => 1, 'max' => 10)
```
若傳遞的參數長度過長,如&username=alonglonglonglongname,則會異常失敗返回:
```javascript
{"ret":400,"data":[],"msg":"Illegal Param: username.len should <= 10, but now username.len = 21"}
```
但是當需要驗證的是類型是中文的話會出現一點問題一個中文字符會占用3個字節所以在min和max驗證的時候會出現一些問題,PhalApi提供了format方式對你需要驗證長度的string進行指定格式可以排除此問題
```javascript
array('name' => 'username', 'type' => 'string','format' => 'utf8', 'min' => 1, 'max' => 10)
```
###(2)整型 int
如通常數據庫中的id,即可配置成:
```javascript
array('name' => 'id', 'type' => 'int', 'require' => true, 'min' => 1 )
```
當傳遞的參數,不在其配置的范圍內時,如&id=0,則會異常失敗返回:
```javascript
{"ret":400,"data":[],"msg":"Illegal Param: id should >= 1, but now id = 0"}
```
###(3)浮點 float
浮點型,類似整型的配置,此處略。
###(4)布爾值 boolean
布爾值,主要是可以對一些字符串轉換成布爾值,如ok, true, success, on, yes, 以及會被PHP解析成true的字符串,都會轉換成true,方便調用。如通常的是否記住我:
```javascript
array('name' => 'isRememberMe', 'type' => 'boolean', 'default' => true)
```
###(5)日期 date
日期可以按自己約定的格式傳遞,當需要將字符串的日期轉換成timestamp時,可以這樣配置:
```javascript
array('name' => 'registerData', 'type' => 'date')
```
對應?isterData=2015-01-31%2010:00:00則會被獲取到為:"2015-01-31 10:00:00"。
如果是配置成:
```javascript
array('name' => 'registerData', 'type' => 'date', 'format' => 'timestamp')
```
則上面的參數再請求時,則會被轉換成:1422669600。
###(6)數組 array
很多時候在接口進行批量獲取時,都需要提供一組參數,所以這時可以使用數組來進行配置。如:
```javascript
array('name' => 'uids', 'type' => 'array', 'format' => 'explode', 'separator' => ',')
```
對應&uids=1,2,3則會被轉換成:
```javascript
array ( 0 => '1', 1 => '2', 2 => '3', )
```
又如接口需要使用JSON來傳遞整塊參數時,可以這樣配置:
```javascript
array('name' => 'params', 'type' => 'array', 'format' => 'json')
```
對應¶ms={"username":"test","password":"123456"}則會被轉換成:
```javascript
array ( 'username' => 'test', 'password' => '123456', )
```
特別地,當配置成了數組,卻未指定格式format時,會轉換成一個元素的數組,如:&name=test,會轉換成:array('test')。
###(7)枚舉 enum
在需要對接口參數進行范圍限制時,可以使用此枚舉型。如對于性別的參數,可以這樣配置:
```javascript
array('name' => 'sex', 'type' => 'enum', 'range' => array('female', 'male'))
```
當傳遞的參數不合法時,如&sex=unknow,則會被攔截,返回失敗:
```javascript
{"ret":400,"data":[],"msg":"Illegal Param: sex should be in female\/male, but now sex = unknow"}
```
關于枚舉類型的配置,這里需要特別注意配置時,應盡量使用字符串的值。
因為通常而言,接口通過GET/POST方式獲取到的參數都是字符串的,而如果配置規則時指定范圍用了整型,會導致底層規則驗證時誤。如:
```javascript
//接口參數為: &type=N
//接口參數規則為:
array('name' => 'type', 'type' => 'enum', 'range' => array(0, 1, 2))
//誤判,因為:
var_dump(in_array('N', array(0, 1, 2))); //結果為true,因為 'N' == 0
```
為了避免這類情況發生,應該這樣配置:
```javascript
//接口參數規則為(使用字符串):
array('name' => '&type', 'type' => 'enum', 'range' => array(`0`, `1`, `2`))
```
###(8)文件 file
在需要對上傳的文件進行過濾、接收和處理時,可以使用文件類型,如:
```javascript
array(
'name' => 'upfile',
'type' => 'file',
'min' => 0,
'max' => 1024 * 1024,
'range' => array('image/jpeg', 'image/png') ,
'ext' => array('txt','xml')
)
```
其中,min和max分別對應文件大小的范圍,單位為字節;range為允許的文件類型,使用數組配置,且不區分大小寫。
如果成功,返回的值對應的是$_FILES["upfile"],即會返回:
```javascript
array(
'name' => '',
'type' => '',
'size' => '',
'tmp_name' => '',
)
```
對應的是:
+ $_FILES["upfile"]["name"] - 被上傳文件的名稱
+ $_FILES["upfile"]["type"] - 被上傳文件的類型
+ $_FILES["upfile"]["size"] - 被上傳文件的大小,以字節計
+ $_FILES["upfile"]["tmp_name"] - 存儲在服務器的文件的臨時副本的名稱
+ $_FILES["upfile"]["error"] - 由文件上傳導致的錯誤代碼
若需要配置默認值default選項,則也應為一數組,且其格式應類似如上。
其中,ext是對文件后綴名進行驗證,當如果上傳文件后綴名不匹配時將拋出異常。文件擴展名的過濾可以類似這樣進行配置:
```
//單個后綴名 - 數組形式
'ext' => array('jpg')
//單個后綴名 - 字符串形式
'ext' => 'jpg'
//多個后綴名 - 數組形式
'ext' => array('jpg', 'jpeg', 'png', 'bmp')
//多個后綴名 - 字符串形式(以英文逗號分割)
'ext' => 'jpg,jpeg,png,bmp'
```
###(9)回調 callable
當需要利用已有函數進行自定義驗證時,可采用回調參數規則,如:
```
//配置規則
array('name' => 'version', 'type' => 'callable', 'callback' => array('Common_MyVersion', 'formatVersion'))
```
然后,回調時將調用下面這個函數:
```
//新增一個自定義的版本檢測函數
class Common_MyVersion {
public static function formatVersion($value, $rule) {
if (count(explode('.', $value)) < 3) {
throw new PhalApi_Exception_BadRequest('版本號格式錯誤');
}
}
}
```
> 溫馨提示:
> 第一個為參數值,第二個為所配置的規則,第三個參數為配置規則中的params(可忽略)
##1.12.8 關于參數設計的原則
###(1)通配的$_REQUEST
使用$_REQUEST獲取參數,便于在不同場合下GET/POST之間的切換,同時在初始化DI()->request服務時,可以指定傳遞的參數,以便于靈活的單元測試;
###(2)更自由的名稱映射
之所以沒把規則配置的下標默認成與客戶端傳遞的name一致,是為了更自由的名稱映射;
如可能我們PHP后臺開發喜歡用駝峰法來表示,但客戶端想用下劃線來分割,則通過這樣配置:
```javascript
array(
'isRememberMe' => array('name' => 'is_remember_me', 'type' => 'boolean', 'default' => true),
)
```
更重要的是,有時我們希望能縮短客戶端請求的參數名稱以節省流量時,可以這樣配置:
```javascript
array(
'isRememberMe' => array('name' => 're', 'type' => 'boolean', 'default' => true),
)
```
###(3)異常返回
對于客戶端參數不合法時,以異常失敗返回,而不是隱性地轉換,是因為后臺接口往往需要手動對傳遞的參數進行人工的驗證,而不是希望得到隱性轉換的值。即當客戶端參數傳遞不對時,我們需要明確提示說:參數非法。
##1.12.9 擴展你的參數
當PhalApi提供的參數規則不能滿足接口參數的規則驗證時,除了使用callable類型進行擴展外,還可以擴展PhalApi_Request_Formatter接口來定制項目需要的類型。
一如既往,分兩步:
+ 1、擴展實現PhalApi_Request_Formatter接口
+ 2、在DI注冊你的類型
下面以大家所熟悉的郵件類型為例,說明擴展的步驟。
首先,我們需要一個實現了郵件類型驗證的功能類:
```
<?php
class Common_MyFormatter_Email implements PhalApi_Request_Formatter {
public function parse($value, $rule) {
if (!preg_match('/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/', $value)) {
throw new PhalApi_Exception_BadRequest('郵箱地址格式錯誤');
}
return $value;
}
}
```
然后,注冊一下:
```
DI()->_formatterEmail = 'Common_MyFormatter_Email';
```
> 溫馨提示:
> 在DI中手動注冊服務時,名稱的格式為: 下劃線("_") + 統一前綴("formatter") + 參數類型(全部小寫后,首字母大寫);
> 若需要實現自動注冊,擴展的類名格式須為:
```
class PhalApi_Request_Formatter_{類型名稱} implements PhalApi_Request_Formatter { ...
```
系統已自動注冊的格式化服務有:
+ _formatterArray 數組格式化服務
+ _formatterBoolean 布爾值格式化服務
+ _formatterCallable 回調格式化服務
+ _formatterDate 日期格式化服務
+ _formatterEnum 枚舉格式化服務
+ _formatterFile 上傳文件格式化服務
+ _formatterFloat 浮點數格式化服務
+ _formatterInt 整數格式化服務
+ _formatterString 字符串格式化服務
至此,便可使用自己定制的類型規則了,
```
array('name' => 'user_email', 'type' => 'email')
```
- 歡迎使用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:接口文檔參考模板