Swagger大大降低了接口提供者和接入者之間的溝通和維護(hù)成本,如果你還不了解Swagger的話,可以看我的另一篇文章《Laravel(PHP)使用Swagger生成API文檔不完全指南 - 基本概念和環(huán)境搭建》
在PHP中使用Swagger,大多都會(huì)用到zircote/swagger-php這個(gè)Composer庫(kù)。以Laravel項(xiàng)目為例,我們通常會(huì)為每個(gè)Controller的返回寫一個(gè)單獨(dú)的Swagger Definition以方便管理,然后在Controller的Annotation中寫下這樣的規(guī)則:
<?php
// ...
/**
* 假設(shè)是項(xiàng)目中的一個(gè)API
*
* @SWG\Get(path="/swagger/my-data",
* tags={"project"},
* summary="拿一些神秘的數(shù)據(jù)",
* description="請(qǐng)求該接口需要先登錄。",
* operationId="getMyData",
* produces={"application/json"},
* @SWG\Parameter(
* in="formData",
* name="reason",
* type="string",
* description="拿數(shù)據(jù)的理由",
* required=true,
* ),
* @Swagger\Annotations\Schema(ref="#/definitions/MyDataResponse"),
* )
*/
public function getMyData()
{
//todo 待實(shí)現(xiàn)
}
// ...
而上面引用的MyDataResponse的定義看起來可能是這樣:
<?php
/**
* @SWG\Definition
*/
class MyDataResponse
{
/**
* @var string
* @SWG\Property(example="Alan Jones")
*/
public $data;
}
注意,$data字段定義中設(shè)置了example屬性,這實(shí)際上是給$data字段舉了一個(gè)返回值的例子,這樣不光可以把Swagger定義導(dǎo)入工具中做接口Mock(example即是Mock接口的返回值)、在Swagger UI返回格式同樣也一目了然:

但有時(shí)這些簡(jiǎn)單的整型或者字符串example就無法滿足項(xiàng)目需求了,例如你可能會(huì)需要返回這樣一個(gè)數(shù)據(jù)結(jié)構(gòu):
{
"data": {
"current_level": 1,
"machine_detail": {
"sn": "77777777",
"mode": "extreme"
}
"records": [
{
"time": "2017-03-28 00:00:00",
"message": "machine started"
}
]
}
}
正常來講,我們應(yīng)該針對(duì)例子中的data、machine_detail和record(records中的每一個(gè)元素)分別建立Definition,然后在定義中去寫引用(ref=)。但有時(shí)我們就是突然感覺很懶啊!又或者這些數(shù)據(jù)結(jié)構(gòu)只有這一個(gè)接口使用,實(shí)在不值當(dāng)單獨(dú)定義幾個(gè)Definition去實(shí)現(xiàn)?。ㄟ€是懶)!
那么怎么辦呢?我簡(jiǎn)單總結(jié)了三個(gè)方法。
1. 直接把復(fù)雜結(jié)構(gòu)寫在example中
如下:
<?php
//...
/**
* @SWG\Property(
* example={"current_level": 1, [省略] "records": { { "time" [繼續(xù)省略] } } },
* )
* @var object
*/
public $data;
//...
這種做法最大的壞處就是在注釋中排版實(shí)在很痛苦……另外要注意得把JSON的數(shù)組括號(hào)(方括號(hào))寫成花括號(hào)(這是zircote/swagger-php的限制)。
2. 使用JSON文件定義
我們可以單獨(dú)把這一個(gè)$data的Definition寫在一個(gè)JSON文件中,如data.json,然后在注釋(Annotation)中寫一個(gè)引用:
<?php
//...
/**
* @SWG\Property(
* ref="data.json",
* )
*/
public $data;
//...
data.json內(nèi)容為:
{
"current_level": 1,
"machine_detail": {
"sn": "77777777",
"mode": "extreme"
}
"records": [
{
"time": "2017-03-28 00:00:00",
"message": "machine started"
}
]
}
這樣Swagger UI在加載完之后還會(huì)去請(qǐng)求data.json來獲取定義內(nèi)容,最終效果是一樣的。但壞處是一部分Definition被拆到了另一個(gè)文件,一個(gè)JSON搞不定,而且請(qǐng)求多了也會(huì)慢。
3. 靈活使用zircote/swagger-php
讓我們先回頭看看是怎么使用zircote/swagger-php返回JSON格式Swagger 定義的:
<?php
// ...
/**
* @SWG\Swagger(
* @SWG\Info(
* title="我的`Swagger`API文檔",
* version="1.0.0"
* )
* )
*/
public function getJSON()
{
$swagger = \Swagger\scan(app_path('Http/Controllers/'));
return response()->json($swagger, 200); //注意這一句我們直接把$swagger傳給了json()方法
}
// ...
注意示例中的$swagger對(duì)象。
在調(diào)用\Swagger\scan()方法時(shí),實(shí)際上是掃描你指定的所有目錄和文件,將其中符合規(guī)則的Swagger Annotation解析出來,并轉(zhuǎn)換為各種Class(在Swagger\Annotations名字空間下可以找到),最終這些Annotation對(duì)象都會(huì)被加載到$swagger對(duì)象里(Swagger\Annotations\Swagger)。$swagger是一個(gè)JsonSerializable,所以可以直接作為json_encode()函數(shù)的參數(shù),在轉(zhuǎn)換過程中,內(nèi)部的各種定義對(duì)象就會(huì)被處理成一個(gè)可以JSON化的stdClass。
那么我們其實(shí)可以把最終生成的數(shù)據(jù)拿到,然后把復(fù)雜的定義直接寫成PHP數(shù)組,在最后和$swagger轉(zhuǎn)換結(jié)果中的definitions進(jìn)行合并就可以了。之前也試過直接手動(dòng)新建Swagger\Annotations\Definition對(duì)象,然后合并到$swagger->definitions數(shù)組中,但發(fā)現(xiàn)這寫起來遠(yuǎn)沒有直接寫數(shù)組的效率高。
在項(xiàng)目中我將這些手寫的Definition分文件存放,然后寫了一個(gè)方法加載,最后合并到返回中。
上面例子的文件內(nèi)容可以寫成這樣:
<?php
return [
"current_level" => 1,
"machine_detail" => [
"sn" => "77777777",
"mode" => "extreme"
]
"records" => [
[
"time" => "2017-03-28 00 =>00 =>00",
"message" => "machine started"
]
]
];
以及改過之后的getJSON()方法:
<?php
// ...
/**
* @SWG\Swagger(
* @SWG\Info(
* title="我的`Swagger`API文檔",
* version="1.0.0"
* )
* )
*/
public function getJSON()
{
$swagger = \Swagger\scan(app_path('Http/Controllers/'));
return response()->json(
mergeWithRawDefinitions($swagger, loadRawDefinition(app_path('Swagger/Raw/'))),
200
);
}
// ...
本文僅僅是拋磚引玉,如果你有更“懶”的方法,歡迎在評(píng)論中與大家分享!