在PHP中寫復(fù)雜的Swagger定義時(shí)如何偷懶(基于zircote/swagger-php)

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返回格式同樣也一目了然:

Property定義了example之后在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_detailrecordrecords中的每一個(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è)$dataDefinition寫在一個(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)論中與大家分享!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容