# Api接口服務層
**Api接口層**稱為接口服務層,負責對客戶端的請求進行響應,處理接收客戶端傳遞的參數,進行高層決策并對領域業務層進行調度,最后將處理結果返回給客戶端。
## 接口參數規則配置
接口參數,對于接口服務本身來說,是非常重要的。對于外部調用的客戶端來說,同等重要。對于接口參數,我們希望能夠既減輕后臺開發對接口參數獲取、判斷、驗證、文檔編寫的痛苦;又能方便客戶端快速調用,明確參數的意義。由此,我們引入了**參數規則**這一概念,即:通過配置參數的規則,自動實現對參數的獲取和驗證,同時自動生成在線接口文檔。
### 一個簡單的示例
假設我們現在需要提供一個用戶登錄的接口,接口參數有用戶名和密碼,那么新增的接口類和規則如下:
```php
// 文件 ./src/app/Api/User.php
<?php
namespace App\Api;
use PhalApi\Api;
class User extends 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);
}
}
```
當請求此接口服務,并類似這樣帶上username和password參數時:
```
/?s=User.Login&username=dogstar&password=123456
```
就可以得到這樣的返回結果。
```
{"ret":0,"data":{"username":"dogstar","password":"123456"},"msg":""}
```
### 參數規則格式
參數規則是針對各個接口服務而配置的多維規則數組,由接口類的```getRules()```方法返回。其中,
+ 一維下標是接口類的方法名,對應接口服務的Action;
+ 二維下標是類屬性名稱,對應在服務端獲取通過驗證和轉換化的最終客戶端參數;
+ 三維下標```name```是接口參數名稱,對應外部客戶端請求時需要提供的參數名稱。
小結如下:
```php
public function getRules() {
return array(
'接口類方法名' => array(
'接口類屬性' => array('name' => '接口參數名稱', ... ... ),
),
);
}
```
在接口實現類里面```getRules()```成員方法配置參數規則后,便可以通過類屬性的方式,根據配置指定的名稱獲取對應的接口參數,如上面的:```$this->username```和```$this->password```。
### 三級參數規則配置
參數規則主要有三種,分別是:系統參數規則、應用參數規則、接口參數規則。
#### 系統參數
系統參數是指被框架保留使用的參數。目前已被PhalApi占用的系統參數只有一個,即:service參數(縮寫為s參數),前面已有介紹。
#### 應用參數
應用參數是指在一個接口系統中,全部項目的全部接口都需要的參數,或者通用的參數。假如我們的商城接口系統中全部的接口服務都需要必須的簽名sign參數,以及非必須的版本號,則可以在```./config/app.php```中的```apiCommonRules```進行應用參數規則的配置:
```php
<?php
return array(
/**
* 應用接口層的統一參數
*/
'apiCommonRules' => array(
//簽名
'sign' => array(
'name' => 'sign', 'require' => true,
),
//客戶端App版本號,默認為:1.4.0
'version' => array(
'name' => 'version', 'default' => '1.4.0',
),
),
)
```
#### 接口參數
接口參數是指各個具體的接口服務所需要的參數,為特定的接口服務所持有,獨立配置。并且進一步在內部又細分為兩種:
+ **通用接口參數規則**:使用```*```作為下標,對當前接口類全部的方法有效。
+ **指定接口參數規則**:使用方法名作為下標,只對接口類的特定某個方法有效。
例如為了加強安全性,需要為全部的用戶接口服務都加上長度為4位的驗證碼參數:
```php
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),
),
);
}
```
現在,當再次請求用戶登錄接口,除了要提供用戶名和密碼外,我們還要提供驗證碼code參數。并且,對于Api\User類的其他方法也一樣。
#### 多個參數規則時的優先級
當同一個參數規則分別在應用參數、通用接口參數及指定接口參數出現時,后面的規則會覆蓋前面的,即具體化的規則會替換通用的規則,以保證接口參數滿足特定場合的定制要求。
簡而言之,多個參數規則的優先級從高到下,分別是(正如你想到的那樣):
+ 1、指定接口參數規則
+ 2、通用接口參數規則
+ 3、應用參數規則
+ 4、系統參數規則
## 參數規則配置詳細說明
具體的參數規則,根據不同的類型有不同的配置選項,以及一些公共的配置選項。目前,主要的類型有:字符串、整數、浮點數、布爾值、時間戳/日期、數組、枚舉類型、文件上傳和回調函數。
類型 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,以及其他PHP作為TRUE的值
時間戳/日期|date|TRUE/FALSE,默認FALSE|日期字符串|可選,僅當為format配置為timestamp時才判斷,且最值應為時間戳|format選項用于配置格式,為timestamp時會將字符串的日期轉換為時間戳
數組|array|TRUE/FALSE,默認FALSE|字符串或者數組,為非數組會自動轉換/解析成數組|可選,判斷數組元素個數|format選項用于配置數組和格式,為explode時根據separator選項將字符串分割成數組, 為json時進行JSON解析
枚舉|enum|TRUE/FALSE,默認FALSE|應為range選項中的某個元素|---|必須的range選項,為一數組,用于指定枚舉的集合
文件|file|TRUE/FALSE,默認FALSE|數組類型|可選,用于表示文件大小范圍,單位為B|range選項用于指定可允許上傳的文件類型;ext選項用于表示需要過濾的文件擴展名
回調|callable/callback|TRUE/FALSE,默認FALSE|---|---|callable/callback選項用于設置回調函數,params選項為回調函數的第三個參數(另外第一個為參數值,第二個為所配置的規則)
### 公共配置選項
公共的配置選項,除了上面的類型、參數名稱、是否必須、默認值,還有說明描述、數據來源。下面分別簡單說明。
+ **類型 type**
用于指定參數的類型,可以是string、int、float、boolean、date、array、enum、file、callable,或者自定義的類型。未指定時,默認為字符串。
+ **參數名稱 name**
接口參數名稱,即客戶端需要傳遞的參數名稱。與PHP變量規則一樣,以下劃線或字母開頭。此選項必須提供,否則會提示錯誤。
+ **是否必須require**
為TRUE時,表示此參數為必須值;為FALSE時,表示此參數為可選。未指定時,默認為FALSE。
+ **默認值 default**
未提供接口參數時的默認值。未指定時,默認為NULL。
+ **最小值 min,最大值 max**
部分類型適用。用于指定接口參數的范圍,比較時采用的是閉區間,即范圍應該為:[min, max]。也可以只使用min或max,若只配置了min,則表示:[min, +∞);若只配置了maz,則表示:(-∞, max]。
+ **說明描述 desc**
用于自動生成在線接口詳情文檔,對參數的含義和要求進行扼要說明。未指定時,默認為空字符串。
+ **數據來源 source**
指定當前單個參數的數據來源,可以是post、get、cookie、server、request、header、或其他自定義來源。未指定時,默認為統一數據源。目前支持的source與對應的數據源映射關系如下:
source|對應的數據源
---|---
post | $_POST
get | $_GET
cookie | $_COOKIE
server | $_SERVER
request | $_REQUEST
header | $_SERVER['HTTP_X']
通過source參數可以輕松、更自由獲取不同來源的參數。以下是一些常用的配置示例。
```php
// 獲取HTTP請求方法,判斷是POST還是GET
'method' => array('name' => 'REQUEST_METHOD', 'source' => 'server'),
// 獲取COOKIE中的標識
'is_new_user' => array('name' => 'is_new_user', 'source' => 'cookie'),
// 獲取HTTP頭部中的編碼,判斷是否為utf-8
'charset' => array('name' => 'Accept-Charset', 'source' => 'header'),
```
若配置的source為無效或非法時,則會拋出異常。如配置了```'source' => 'NOT_FOUND'```,會得到:
```
"msg": "服務器運行錯誤: 參數規則中未知的數據源:NOT_FOUND"
```
### 9種參數類型
對于各種參數類型,結合示例說明如下。
+ **字符串 string**
當一個參數規則未指定類型時,默認為string。如最簡單的:
```php
array('name' => 'username')
```
> **溫馨提示:**這一小節的參數規則配置示例,都省略了類屬性,以關注配置本身的內容。
這樣就配置了一個參數規則,接口參數名字叫username,類型為字符串。
一個完整的寫法可以為:
```php
array('name' => 'username', 'type' => 'string', 'require' => true, 'default' => 'nobody', 'min' => 1, 'max' => 10)
```
這里指定了為必選參數,默認值為nobody,且最小長度為1個字符,最大長度為10個字符,若傳遞的參數長度過長,如```&username=alonglonglonglongname```,則會異常失敗返回:
```
"msg": "非法請求:username.len應該小于等于10, 但現在username.len = 21"
```
當需要驗證的是中文的話,由于一個中文字符會占用3個字節。所以在min和max驗證的時候會出現一些問題。為此,PhalApi提供了format配置選項,用于指定字符集。如:
```php
array('name' => 'username', 'type' => 'string', 'format' => 'utf8', 'min' => 1, 'max' => 10)
```
我們還可以使用```regex```下標來進行正則表達式的驗證,一個郵箱的例子是:
```php
array('name' => 'email', 'regex' => "/^([0-9A-Za-z\\-_\\.]+)@([0-9a-z]+\\.[a-z]{2,3}(\\.[a-z]{2})?)$/i")
```
+ **整型 int**
整型即自然數,包括正數、0和負數。如通常數據庫中的id,即可配置成:
```php
array('name' => 'id', 'type' => 'int', 'require' => true, 'min' => 1)
```
當傳遞的參數,不在其配置的范圍內時,如```&id=0```,則會異常失敗返回:
```
"msg": "非法請求:id應該大于或等于1, 但現在id = 0"
```
另外,對于常見的分頁參數,可以這樣配置:
```php
array('name' => 'page_num', 'type' => 'int', 'min' => 1, 'max' => 20, 'default' => 20)
```
即每頁數量最小1個,最大20個,默認20個。
+ **浮點 float**
浮點型,類似整型的配置,此處略。
+ **布爾值 boolean**
布爾值,主要是可以對一些字符串轉換成布爾值,如ok,true,success,on,yes,以及會被PHP解析成true的字符串,都會轉換成TRUE。如通常的“是否記住我”參數,可配置成:
```php
array('name' => 'is_remember_me', 'type' => 'boolean', 'default' => TRUE)
```
則以下參數,最終服務端會作為TRUE接收。
```
?is_remember_me=ok
?is_remember_me=true
?is_remember_me=success
?is_remember_me=on
?is_remember_me=yes
?is_remember_me=1
```
+ **日期 date**
日期可以按自己約定的格式傳遞,默認是作為字符串,此時不支持范圍檢測。例如配置注冊時間:
```php
array('name' => 'register_date', 'type' => 'date')
```
對應地,```register_date=2015-01-31 10:00:00```則會被獲取到為:"2015-01-31 10:00:00"。
當需要將字符串的日期轉換成時間戳時,可追加配置選項```'format' => 'timestamp'```,則配置成:
```php
array('name' => 'register_date', 'type' => 'date', 'format' => 'timestamp')
```
則上面的參數再請求時,則會被轉換成:1422669600。
此時作為時間戳,還可以添加范圍檢測,如限制時間范圍在31號當天:
```php
array('name' => 'register_date', 'type' => 'date', 'format' => 'timestamp', 'min' => 1422633600, 'max' => 1422719999)
```
當配置的最小值或最大值為字符串的日期時,會自動先轉換成時間戳再進行檢測比較。如可以配置成:
```php
array('name' => 'register_date', ... ... 'min' => '2015-01-31 00:00:00', 'max' => '2015-01-31 23:59:59')
```
+ **數組 array**
很多時候在接口進行批量獲取時,都需要提供一組參數,如多個ID,多個選項。這時可以使用數組來進行配置。如:
```php
array('name' => 'uids', 'type' => 'array', 'format' => 'explode', 'separator' => ',')
```
這時接口參數```&uids=1,2,3```則會被轉換成:
```php
array ( 0 => '1', 1 => '2', 2 => '3', )
```
如果設置了默認值,那么默認值會從字符串,根據相應的format格式進行自動轉換。如:
```php
array( ... ... 'default' => '4,5,6')
```
那么在未傳參數的情況下,自動會得到:
```php
array ( 0 => '4', 1 => '5', 2 => '6', )
```
又如接口需要使用JSON來傳遞整塊參數時,可以這樣配置:
```php
array('name' => 'params', 'type' => 'array', 'format' => 'json')
```
對應地,接口參數```¶ms={"username":"test","password":"123456"}```則會被轉換成:
```php
array ( 'username' => 'test', 'password' => '123456', )
```
> **溫馨提示:**使用JSON傳遞參數時,建議使用POST方式傳遞。若使用GET方式,須注意參數長度不應超過瀏覽器最大限制長度,以及URL編碼問。
若使用JSON格式時,設置了默認值為:
```php
array( ... ... 'default' => '{"username":"dogstar","password":"xxxxxx"}')
```
那么在未傳參數的情況下,會得到轉換后的:
```php
array ( 'username' => 'dogstar', 'password' => 'xxxxxx', )
```
特別地,當配置成了數組卻未指定格式format時,接口參數會轉換成只有一個元素的數組,如接口參數:```&name=test```,會轉換成:
```php
array ( 0 => 'test' )
```
+ **枚舉 enum**
在需要對接口參數進行范圍限制時,可以使用此枚舉型。如對于性別的參數,可以這樣配置:
```php
array('name' => 'sex', 'type' => 'enum', 'range' => array('female', 'male'))
```
當傳遞的參數不合法時,如```&sex=unknow```,則會被攔截,返回失敗:
```
"msg": "非法請求:參數sex應該為:female/male,但現在sex = unknow"
```
關于枚舉類型的配置,這里需要特別注意配置時,應盡量使用字符串的值。 因為通常而言,接口通過GET/POST方式獲取到的參數都是字符串的,而如果配置規則時指定范圍用了整型,會導致底層規則驗證時誤判。例如接口參數為```&type=N```,而接口參數規則為:
```php
array('name' => 'type', 'type' => 'enum', 'range' => array(0, 1, 2))
```
則會出現以下這樣的誤判:
```php
var_dump(in_array('N', array(0, 1, 2))); // 結果為true,因為 'N' == 0
```
為了避免這類情況發生,應該使用使用字符串配置范圍值,即可這樣配置:
```php
array('name' => 'type', 'type' => 'enum', 'range' => array(`0`, `1`, `2`))
```
+ **文件 file**
在需要對上傳的文件進行過濾、接收和處理時,可以使用文件類型,如:
```php
array(
'name' => 'upfile',
'type' => 'file',
'min' => 0,
'max' => 1024 * 1024,
'range' => array('image/jpeg', 'image/png') ,
'ext' => array('jpeg', 'png')
)
```
其中,min和max分別對應文件大小的范圍,單位為字節;range為允許的文件類型,使用數組配置,且不區分大小寫。
如果成功,返回的值對應的是```$_FILES["upfile"]```,即會返回:
```php
array(
'name' => ..., // 被上傳文件的名稱
'type' => ..., // 被上傳文件的類型
'size' => ..., // 被上傳文件的大小,以字節計
'tmp_name' => ..., // 存儲在服務器的文件的臨時副本的名稱
)
```
對應的是:
+ $_FILES["upfile"]["name"] - 被上傳文件的名稱
+ $_FILES["upfile"]["type"] - 被上傳文件的類型
+ $_FILES["upfile"]["size"] - 被上傳文件的大小,以字節計
+ $_FILES["upfile"]["tmp_name"] - 存儲在服務器的文件的臨時副本的名稱
+ $_FILES["upfile"]["error"] - 由文件上傳導致的錯誤代碼
> 參考:以上內容來自W3School,文件上傳時請使用表單上傳,并enctype 屬性使用"multipart/form-data"。更多請參考[PHP 文件上傳](http://www.w3school.com.cn/php/php_file_upload.asp)。
若需要配置默認值default選項,則也應為一數組,且其格式應類似如上。
其中,ext是對文件后綴名進行驗證,當如果上傳文件后綴名不匹配時將拋出異常。文件擴展名的過濾可以類似這樣進行配置:
+ 單個后綴名 - 數組形式
```php
'ext' => array('jpg')
```
+ 單個后綴名 - 字符串形式
```php
'ext' => 'jpg'
```
+ 多個后綴名 - 數組形式
```php
'ext' => array('jpg', 'jpeg', 'png', 'bmp')
```
+ 多個后綴名 - 字符串形式(以英文逗號分割)
```php
'ext' => 'jpg,jpeg,png,bmp'
```
+ **回調 callable/callback**
當需要利用已有函數進行自定義驗證時,可采用回調參數規則,如配置規則:
```php
array('name' => 'version', 'type' => 'callable', 'callback' => 'App\\Common\\Request\\Version::formatVersion')
```
然后,回調時將調用下面這個新增的類函數:
```php
<?php
namespace App\Common\Request;
use PhalApi\Exception\BadRequestException;
class Version {
public static function formatVersion($value, $rule) {
if (count(explode('.', $value)) < 3) {
throw new BadRequestException('版本號格式錯誤');
}
return $value;
}
}
```
回調函數的簽名為:```function format($value, $rule, $params)```,第一個為參數原始值,第二個為所配置的規則,第三個可選參數為配置規則中的params選項。最后應返回轉換后的參數值。
## 接口返回
回顧一下,在PhalApi中,掊口返回的結果的結構為:
```html
{
"ret": 200, // 狀態碼
"data": {
// 業務數據
},
"msg": "" // 錯誤提示信息
}
```
### 正常情況下的返回
正常情況下,在Api層返回的數據結果,會在返回結果的data字段中體現。例如:
```php
class Hello extends Api {
public function world() {
return array('title' => 'Hello World!');
}
}
```
對應:
```
{
"ret": 200,
"data": {
"title": "Hello World!"
},
"msg": ""
}
```
成功返回時,狀態碼ret為200,并且錯誤信息msg為空。
### 失敗情況下的返回
對于異常情況,包括系統錯誤或者應用層的錯誤,可以通過拋出[PhalApi\Exception](https://github.com/phalapi/kernal/blob/master/src/Exception.php)系列的異常,中斷請求并返回相關的錯誤信息。例如:
```php
class Hello extends Api {
public function fail() {
throw new BadRequestException('簽名失敗', 1);
}
}
```
會得到以下結果輸出:
```
{
"ret": 401,
"data": [],
"msg": "Bad Request: 簽名失敗"
}
```
## 注釋與在線文檔
PhalApi提供了自動生成的在線接口文檔,對于每一個接口服務,都有對應的在線接口詳情文檔。如默認接口服務```Site.Index```的在線接口詳情文檔為:

此在線接口詳情文檔,從上到下,依次說明如下。
### 接口服務名稱
接口服務名稱是指用于請求時的名稱,對應s參數(或service參數)。接口服務的中文名稱,為不帶任何注解的注釋,通常為接口類成員函數的第一行注釋。
```php
class Site extends Api {
/**
* 默認接口服務
*/
public function index() {
}
}
```
### 接口說明
接口說明對應接口類成員函數的```@desc```注釋。
```php
class Site extends Api {
/**
* 默認接口服務
* @desc 默認接口服務,當未指定接口服務時執行此接口服務
*/
public function index() {
}
}
```
### 接口參數
接口參數是根據接口類配置的參數規則自動生成,即對應當前接口類```getRules()```方法中的返回。其中最后的“說明” 字段對應參數規則中的desc選項。可以配置多個參數規則。此外,配置文件./config/app.php中的公共參數規則也會顯示在此接口參數里。
```php
class Site extends Api {
public function getRules() {
return array(
'index' => array(
'username' => array('name' => 'username', 'default' => 'PHPer', ),
),
);
}
}
```
### 返回結果
返回結果對應接口類成員函數的```@return```注釋,可以有多組,格式為:```@return 返回類型 返回字段 說明```。
```php
class Site extends Api {
/**
* 默認接口服務
* @desc 默認接口服務,當未指定接口服務時執行此接口服務
* @return string title 標題
* @return string content 內容
* @return string version 版本,格式:X.X.X
* @return int time 當前時間戳
*/
public function index() {
}
}
```
### 異常情況
異常情況對應```@exception```注釋,可以有多組,格式為:```@exception 錯誤碼 錯誤描述信息```。例如:
```php
/**
* @exception 406 簽名失敗
*/
public function index() {
```
刷新后,可以看到新增的異常情況說明。
### 公共注釋
對于當前類的全部函數成員的公共```@exception```異常情況注釋和```@return```返回結果注釋,可在類注釋中統一放置。而對于多個類公共的@exception```和```@return```注釋,則可以在父類的類注釋中統一放置。
也就是說,通過把```@exception```注解和```@return```注解移到類注釋,可以添加全部函數成員都適用的注解。例如,Api\User類的全部接口都返回code字段,且都返回400和500異常,則可以:
```php
<?php
namespace App\Api;
use PhalApi\Api;
/**
* @return int code 操作碼,0表示成功
* @exception 400 參數傳遞錯誤
* @exception 500 服務器內部錯誤
*/
class User extends Api {
```
這樣就不需要在每個函數成員的注釋中重復添加注解。此外,也可以在父類的注釋中進行添加。對于相同異常碼的```@exception```注解,子類的注釋會覆蓋父類的注釋,方法的注釋會覆蓋類的注釋;而對于相同的返回結果```@return```注釋,也一樣。
需要注意的是,注釋必須是緊挨在類的前面,而不能是在namespace前面,否則會導致注釋解析失敗。