“播下一種思想,收獲一種行為;播下一種行為,收獲一種習慣;播下一種習慣,收獲一種性格;播下一種性格,收獲一種命運?!? --《成君憶:水煮三國》
參數(shù),對于接口來說,是非常重要的輸入。對于外部調用來說,同等重要。
因此,對于參數(shù)這塊,我們是希望能夠既減輕后臺開發(fā)對接口參數(shù)獲取、判斷、驗證、文檔編寫的痛苦;又便于客戶端方便的、自由的調用;既利已又利他。
由此,我們引入了 參數(shù)解析 這一概念,即:通過配置參數(shù)的規(guī)則,即可自動實現(xiàn)參數(shù)的獲取和驗證。
熟悉Yii的同學,對于以下的規(guī)則配置應該倍感親切,但是不熟悉的同學也可以同樣快速上手。因為,你會慢慢發(fā)現(xiàn),這樣的規(guī)則很符合我們PHP開發(fā)的規(guī)范,如果沒有,我們繼續(xù)努力改進。
格式如下:
array(
'參數(shù)名' => array('name' => '接口參數(shù)名稱', 'type' => '類型', 'default' => '默認值', ...),
... ...
)
假設這樣的業(yè)務場景,我們需要提供一個用戶登錄的接口,其中需要用戶名和密碼,因此:
<?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
就可以獲取到需要的參數(shù):
{"ret":0,"data":{"username":"test","password":"123456"},"msg":""}
從中,可以很容易理解:參數(shù)規(guī)則需要統(tǒng)一配置在接口實現(xiàn)類里面的 getRules() 函數(shù),隨后即可以通過類成員屬性方式獲取,如: $this->username 。
很多時候我們都會對用戶名和密碼作一些驗證,如是否必須、長度、最值,以及默認值等。
繼續(xù)上面的業(yè)務場景,我們登錄下用戶名和密碼必須,且密碼長度至少為6個字符,則可以調整參數(shù)規(guī)則:
'login' => array(
'username' => array('name' => 'username', 'require' => true),
'password' => array('name' => 'password', 'require' => true, 'min' => 6),
),
嘗試一下非法的參數(shù)請求,如無任何參數(shù)的情況下,訪問/?service=User.Login,返回:
{"ret":400,"data":[],"msg":"Illegal Param: wrong param: username"}
再嘗試一下密碼長不對的情況,訪問/?service=User.Login&username=test&password=123,返回:
{"ret":400,"data":[],"msg":"Illegal Param: password.len should >= 6, but now password.len = 3"}
已被系統(tǒng)固定占有的參數(shù),目前只有一個,即:service,為需要調用的服務,類型為字符串,格式為:XXX.XXX,首字母不區(qū)分大小寫,建議統(tǒng)一以大寫開頭。
以下是一些示例:
#推薦寫法
/?service=User.GetBaseInfo
#正確寫法(開頭小寫)
/?service=user.getBaseInfo
#正確寫法(方法名小寫,但類名只能開頭小寫,否則會導致linux系統(tǒng)下文件加載失?。?/?service=user.getbaseinfo
#錯誤寫法(缺少方法名)
/?service=User
#錯誤寫法(缺少點號分割)
/?service=UserGetBaseInfo
#錯誤寫法(默認只支持點號分割)
/?service=User|GetBaseInfo
應用參數(shù)是指在一個項目中,全部接口都需要的參數(shù),或者通用的參數(shù)規(guī)則。假如我們的項目中全部需要簽名sign參數(shù),且必須;以及非必須的版本號,則可以在./Config/app.php中的apiCommonRules配置:
//$vim ./Config/app.php
<?php
return array(
/**
* 應用接口層的統(tǒng)一參數(shù)
*/
'apiCommonRules' => array(
//簽名
'sign' => array(
'name' => 'sign', 'require' => true,
),
//客戶端App版本號,如:1.0.1
'version' => array(
'name' => 'version', 'default' => '',
),
),
... ...
接口參數(shù)即為上面在各個接口子類中配置的規(guī)則,為特定接口所持有。同時,為了方便同一套接口的規(guī)則重用,可以使用下標為 '*' 表示是本接口通用規(guī)則,如我們?yōu)榱思訌姲踩?,為全部的用戶接口操作都加?位的驗證碼:
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),
),
);
}
在完成對上面的應用參數(shù)規(guī)則、接口通用規(guī)則和指定規(guī)則的參數(shù)進行配置后,對用戶登錄的接口進行請求時就需要這樣訪問:
/?service=User.login&sign=77f81c17d512302383e5f26b99dae4d9&username=test&password=123456&code=abcd
溫馨提示:在Api類里面配置規(guī)則時,下標不區(qū)分大小寫。因為框架會自動將請求的函數(shù)名和全部的規(guī)則下標轉換成小寫進行匹配。
這里,再小結一下,接口參數(shù)可以分為兩種: 通用接口參數(shù) 和 指定接口參數(shù) 。前者用 * 號下標表示,后者則用函數(shù)名作為下標表示。
當同一個參數(shù)規(guī)則分別在應用參數(shù)、接口通用參數(shù)及特定接口參數(shù)出現(xiàn)時,后面的規(guī)則會覆蓋前面的,即具體化的規(guī)則會替換通用的規(guī)則,以保證接口在特定場合的定制性。
簡而言之,多個參數(shù)規(guī)則的優(yōu)先級從高到下,分別是(正如你想到的那樣):
為了便于理解上面全部的參數(shù)規(guī)則,對于具體接口調用的要求,這里可以使用在線接口參數(shù)查詢工具在瀏覽器訪問查看:
/demo/checkApiParams.php?service=User.Login
可以看到:
此工具同時也可以方便客戶端實時查看接口文檔時,進行輔助的接口規(guī)則說明。
這里值得一提的是,我們這里所定義的參數(shù)規(guī)則實際上也是自描述數(shù)據(jù)。即配置的代碼真實同步反映了參數(shù)的相關屬性。
系統(tǒng)下GET和POST皆可,但是推薦:
1、service參數(shù)以GET方式傳遞,接口統(tǒng)一以/?service=XXX.XXX鏈接請求,便于交流,更重要的是當接口發(fā)生問題時,可以快速在服務器上通過nginx日志定位問題;
2、其他參數(shù)以POST方式傳遞,特別對于敏感數(shù)據(jù),如密碼,以相對保護數(shù)據(jù)安全;
3、在編寫文檔,或者進行調試時,可以全部臨時使用GET方式,如本文檔的寫法,同時在瀏覽器時也可以使用GET;
類型type | 參數(shù)名稱 name | 是否必須require | 默認值default | 最小值&最大值min&max | 更多 |
---|---|---|---|---|---|
字符串 | string | true/false,默認false | 應為字符串 | 可選 | regex下標為正則匹配的規(guī)則;format下標可用于定義字符編碼的類型,如utf8、gbk,gb2312 |
整數(shù) | int | true/false,默認false | 應為整數(shù) | 可選 | --- |
浮點數(shù) | float | true/false,默認false | 應為浮點數(shù) | 可選 | --- |
布爾值 | boolean | true/false,默認false | true/false | --- | 以下值會轉換為true: ok, true, success, on, yes, 1 |
時間戳/日期 | date | true/false,默認false | 會按格式轉換 | 可選,僅當為timestamp時才判斷 | 格式:format 為timestamp時會將字符串的日期轉換 |
數(shù)組 | array | true/false,默認false | 為非數(shù)組會自動轉換/解析成數(shù)組 | 可選,判斷數(shù)組元素個數(shù) | 格式:format 為explode時,會根據(jù)separator將字符串分割成數(shù)組, 為json時,會json解析 |
枚舉 | enum | true/false,默認false | 應為range中的某個元素 | --- | 必須,range,以數(shù)組指定枚舉的范圍 |
文件 | file | true/false,默認false | 數(shù)組類型 | min和max表示文件大小范圍 | range下標表示允許上傳的文件類型,ext表示需要過濾的文件擴展名 |
回調 | callable | true/false,默認false | --- | callback設置回調函數(shù),params為回調函數(shù)的第三個參數(shù),第一個為參數(shù)值,第二個為所配置的規(guī)則 |
溫馨提示:
全部的參數(shù)規(guī)則,都可以配置desc下標,對應在線接口文檔的”說明“部分。
如: array('name' => 'username', 'desc' => '用戶名')
下面是對各類型的示例說明。
當一個參數(shù)規(guī)則 未指定類型時,默認為string。一個完整的寫法可以為:
array('name' => 'username', 'type' => 'string', 'require' => true, 'default' => 'nobody', 'min' => 1, 'max' => 10)
若傳遞的參數(shù)長度過長,如&username=alonglonglonglongname,則會異常失敗返回:
{"ret":400,"data":[],"msg":"Illegal Param: username.len should <= 10, but now username.len = 21"}
但是當需要驗證的是類型是中文的話會出現(xiàn)一點問題一個中文字符會占用3個字節(jié)所以在min和max驗證的時候會出現(xiàn)一些問題,PhalApi提供了format方式對你需要驗證長度的string進行指定格式可以排除此問題
array('name' => 'username', 'type' => 'string','format' => 'utf8', 'min' => 1, 'max' => 10)
對于正則表達式的驗證,一個郵箱的例子是:
'email' => array(
'name' => 'email',
'require' => true,
'min' => '1',
'regex' => "/^([0-9A-Za-z\\-_\\.]+)@([0-9a-z]+\\.[a-z]{2,3}(\\.[a-z]{2})?)$/i",
'desc' => '郵箱',
),
如通常數(shù)據(jù)庫中的id,即可配置成:
array('name' => 'id', 'type' => 'int', 'require' => true, 'min' => 1 )
當傳遞的參數(shù),不在其配置的范圍內(nèi)時,如&id=0,則會異常失敗返回:
{"ret":400,"data":[],"msg":"Illegal Param: id should >= 1, but now id = 0"}
浮點型,類似整型的配置,此處略。
布爾值,主要是可以對一些字符串轉換成布爾值,如ok, true, success, on, yes, 以及會被PHP解析成true的字符串,都會轉換成true,方便調用。如通常的是否記住我:
array('name' => 'isRememberMe', 'type' => 'boolean', 'default' => true)
日期可以按自己約定的格式傳遞,當需要將字符串的日期轉換成timestamp時,可以這樣配置:
array('name' => 'registerData', 'type' => 'date')
對應地,risterData=2015-01-31 10:00:00則會被獲取到為:"2015-01-31 10:00:00"。
如果是配置成:
array('name' => 'registerData', 'type' => 'date', 'format' => 'timestamp')
則上面的參數(shù)再請求時,則會被轉換成:1422669600。
很多時候在接口進行批量獲取時,都需要提供一組參數(shù),所以這時可以使用數(shù)組來進行配置。如:
array('name' => 'uids', 'type' => 'array', 'format' => 'explode', 'separator' => ',')
對應&uids=1,2,3則會被轉換成:
array ( 0 => '1', 1 => '2', 2 => '3', )
又如接口需要使用JSON來傳遞整塊參數(shù)時,可以這樣配置:
array('name' => 'params', 'type' => 'array', 'format' => 'json')
對應¶ms={"username":"test","password":"123456"}則會被轉換成:
array ( 'username' => 'test', 'password' => '123456', )
特別地,當配置成了數(shù)組,卻未指定格式format時,會轉換成一個元素的數(shù)組,如:&name=test,會轉換成:array('test')。
在需要對接口參數(shù)進行范圍限制時,可以使用此枚舉型。如對于性別的參數(shù),可以這樣配置:
array('name' => 'sex', 'type' => 'enum', 'range' => array('female', 'male'))
當傳遞的參數(shù)不合法時,如&sex=unknow,則會被攔截,返回失?。?/p>
{"ret":400,"data":[],"msg":"Illegal Param: sex should be in female\/male, but now sex = unknow"}
關于枚舉類型的配置,這里需要特別注意配置時,應盡量使用字符串的值。
因為通常而言,接口通過GET/POST方式獲取到的參數(shù)都是字符串的,而如果配置規(guī)則時指定范圍用了整型,會導致底層規(guī)則驗證時誤。如:
//接口參數(shù)為: &type=N
//接口參數(shù)規(guī)則為:
array('name' => 'type', 'type' => 'enum', 'range' => array(0, 1, 2))
//誤判,因為:
var_dump(in_array('N', array(0, 1, 2))); //結果為true,因為 'N' == 0
為了避免這類情況發(fā)生,應該這樣配置:
//接口參數(shù)規(guī)則為(使用字符串):
array('name' => '&type', 'type' => 'enum', 'range' => array(`0`, `1`, `2`))
在需要對上傳的文件進行過濾、接收和處理時,可以使用文件類型,如:
array(
'name' => 'upfile',
'type' => 'file',
'min' => 0,
'max' => 1024 * 1024,
'range' => array('image/jpeg', 'image/png') ,
'ext' => array('txt','xml')
)
其中,min和max分別對應文件大小的范圍,單位為字節(jié);range為允許的文件類型,使用數(shù)組配置,且不區(qū)分大小寫。
如果成功,返回的值對應的是$_FILES["upfile"],即會返回:
array(
'name' => '',
'type' => '',
'size' => '',
'tmp_name' => '',
)
對應的是:
若需要配置默認值default選項,則也應為一數(shù)組,且其格式應類似如上。
其中,ext是對文件后綴名進行驗證,當如果上傳文件后綴名不匹配時將拋出異常。文件擴展名的過濾可以類似這樣進行配置:
//單個后綴名 - 數(shù)組形式
'ext' => array('jpg')
//單個后綴名 - 字符串形式
'ext' => 'jpg'
//多個后綴名 - 數(shù)組形式
'ext' => array('jpg', 'jpeg', 'png', 'bmp')
//多個后綴名 - 字符串形式(以英文逗號分割)
'ext' => 'jpg,jpeg,png,bmp'
當需要利用已有函數(shù)進行自定義驗證時,可采用回調參數(shù)規(guī)則,如:
//配置規(guī)則
array('name' => 'version', 'type' => 'callable', 'callback' => array('Common_MyVersion', 'formatVersion'))
然后,回調時將調用下面這個函數(shù):
//新增一個自定義的版本檢測函數(shù)
class Common_MyVersion {
public static function formatVersion($value, $rule) {
if (count(explode('.', $value)) < 3) {
throw new PhalApi_Exception_BadRequest('版本號格式錯誤');
}
}
}
溫馨提示:第一個為參數(shù)值,第二個為所配置的規(guī)則,第三個參數(shù)為配置規(guī)則中的params(可忽略)
使用$_REQUEST獲取參數(shù),便于在不同場合下GET/POST之間的切換,同時在初始化DI()->request服務時,可以指定傳遞的參數(shù),以便于靈活的單元測試;
之所以沒把規(guī)則配置的下標默認成與客戶端傳遞的name一致,是為了更自由的名稱映射;
如可能我們PHP后臺開發(fā)喜歡用駝峰法來表示,但客戶端想用下劃線來分割,則通過這樣配置:
array(
'isRememberMe' => array('name' => 'is_remember_me', 'type' => 'boolean', 'default' => true),
)
更重要的是,有時我們希望能縮短客戶端請求的參數(shù)名稱以節(jié)省流量時,可以這樣配置:
array(
'isRememberMe' => array('name' => 're', 'type' => 'boolean', 'default' => true),
)
對于客戶端參數(shù)不合法時,以異常失敗返回,而不是隱性地轉換,是因為后臺接口往往需要手動對傳遞的參數(shù)進行人工的驗證,而不是希望得到隱性轉換的值。即當客戶端參數(shù)傳遞不對時,我們需要明確提示說:參數(shù)非法。
當PhalApi提供的參數(shù)規(guī)則不能滿足接口參數(shù)的規(guī)則驗證時,除了使用callable類型進行擴展外,還可以擴展PhalApi_Request_Formatter接口來定制項目需要的類型。
一如既往,分兩步:
下面以大家所熟悉的郵件類型為例,說明擴展的步驟。
首先,我們需要一個實現(xiàn)了郵件類型驗證的功能類:
<?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中手動注冊服務時,名稱的格式為: 下劃線("_") + 統(tǒng)一前綴("formatter") + 參數(shù)類型(全部小寫后,首字母大寫);
若需要實現(xiàn)自動注冊,擴展的類名格式須為:class PhalApi_Request_Formatter_{類型名稱} implements PhalApi_Request_Formatter { ...
系統(tǒng)已自動注冊的格式化服務有:
至此,便可使用自己定制的類型規(guī)則了,
array('name' => 'user_email', 'type' => 'email')
更多建議: