Swagger 怎麼寫,實戰演練,讓我來一步一步教你…

本文大綱

目錄

前言

上一篇 [比較導入 Api 文件選擇 Swagger or apidocs],文中說明為什麼要導入 Api 文件的原因、要解決的問題,並比較兩款工具的優缺點,選擇導入 Swagger 的原因為何?
這一篇會去紀錄筆者學習 Swagger 文件寫法的雷,並帶出範例,讓讀者可以在應用上能少走一些冤枉路!

Swagger 參數設定

筆者團隊使用 darkaonline/l5-swagger: "6.*",用 composer 進行安裝吧! 此外,VScode extension: Swagger-PHP Annotation v1.1.1 可自動生成標準語法,節省時間。

  • cmd
1
2
3
4
5
6
7
8
composer require darkaonline/l5-swaggerc
php artisan vendor:publish --provider L5Swagger\L5SwaggerServiceProvider // command line
L5Swagger\L5SwaggerServiceProvider::class // config/app.php 加入此ServiceProvider
// app/Provider/AppServiceProvider
public function register()
{
	$this->app->register(\L5Swagger\L5SwaggerServiceProvider::class);
}
接下來執行 php artisan l5-swagger:generate 就可以產生文件囉! 如有遇到問題,可能是你 app/Http/Controllers/Controller.php 沒有加上 OA 規範。詳細可以參考這篇
Controller.php
  • php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
 * @OA\OpenApi(
 *  @OA\Info(
 *      title="Swagger-doc Services API",
 *      version="1.0.0",
 *      description="Swagger Service App",
 *      @OA\Contact(
 *          email="[email protected]"
 *      )
 *  ),
 *  @OA\Server(
 *      description="Swagger-doc App API",
 *      url="https://your-server-domain/api/document"
 *  ),
 *  @OA\PathItem(
 *      path="/"
 *  )
 * )
 */

再看一次 Swagger 提供的 Live demo,這是我們的目標,產出這麼美的文件。

swagger
swagger 精美畫面
swagger
swagger get

大補帖

  1. swagger array 要用 {} 替代 []
  2. tags 代表 Api 分類
  3. path 你的 Api 路徑,也可以於 url 代入參數
  4. summary 描述 Api
  5. @OA\Parameter 說明 url 的 path or query 如何定義
  6. @OA\Property 說明此 Api 屬性屬性,可以用於 Schema, RequestBody, Response 等說明欄位屬性與型態
  7. @OA\Response 描述回傳 response 定義、格式、型態與範例
  8. @OA\MediaType 用來描述內容,可用於 response 內容描述
  9. @OA\Examples 說明不同的情境示範參數
  10. @OA\Schema 定義欄位格式,也可以將規範寫在 model 內,然後引用在 Controller 內,例如:@OA\Schema(ref="#/components/schemas/Promotion") 以下面為 Prmotion 促銷為例,如 requestBody & Response 引用此 Schema 可以讓使用者馬上了解其格式。
    • required 如 POST/Patch 必填
    • example 範例參數
    • readOnly 是否開放更新
    • format 格式
Promotion.php
  • php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/**
 * @OA\Schema(
 *      schema="Promotion",
 *      required={"id", "name", "property-1", "property-2", "property-3"},
 *      @OA\Property(
 *          property="id",
 *          description="id",
 *          type="integer",
 *          format="int32",
 *          example="1",
 *          readOnly=true
 *      ),
 *      @OA\Property(
 *          property="name",
 *          description="name",
 *          type="string",
 *          format="string",
 *          example="促銷方案"
 *      ),
 *      @OA\Property(
 *          property="property-1",
 *          description="property-1",
 *          type="integer",
 *          format="int32",
 *          example="618",
 *      ),
 *      @OA\Property(
 *          property="property-2",
 *          description="property-2",
 *          type="string",
 *          format="float",
 *          example="屬性二"
 *      ),
 *      @OA\Property(
 *          property="property-3",
 *          description="property-3",
 *          type="integer",
 *          format="integer",
 *          example="25",
 *      ),
 *      @OA\Property(
 *          property="_method",
 *          type="string",
 *          description="如為 Update 請幫我填上 PATCH",
 *          example="",
 *      ),
 * )
 */
class Promotion extends Model
{
}
  1. @OA\JsonContent 用來描述內容,可用於 response 內容描述、格式定義,例如:@OA\JsonContent(ref="#/components/schemas/User") 也可以自定義方式:
  • 寫法一:如剛好回傳格式與 Schema 形式相同
PromotionController.php
  • php
1
2
3
4
5
    * @OA\Response(
    *     response=200,
    *     description="successful operation",
    *     @OA\JsonContent(ref="#/components/schemas/Promotion"),
    * )
  • 寫法二:自定義
Controller.php
  • php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
     *   @OA\Response(response=200, description="Successful operation",
     *      content={
     *          @OA\MediaType(
     *              mediaType="application/json",
     *                 example=
     *                    {
     *                      "code": 200,
     *                      "message": "",
     *                      "data": {
     *                         "id": 999,
     *                         "text": "Swagger Api"
     *                      }
     *                   }
     *          )
     *       }
     *   ),
  1. Request Body:定義 payload 要傳的參數與格式,也提供兩種寫法,給讀者參考,取決於 Api 情境與參數形式
  • 寫法一:用於 Post / Patch 有明確定義的 Schema
PromotionController.php
  • php
1
2
3
4
5
6
     *   @OA\RequestBody(
     *      @OA\MediaType(
     *          mediaType="multipart/form-data",
     *          @OA\Schema(ref="#/components/schemas/Promotion")
     *     )
     *   )
  • 寫法二:自定義
PromotionController.php
  • php
1
2
3
4
5
6
7
8
9
10
     *   @OA\RequestBody(
     *      @OA\JsonContent(
     *        type="object",
     *        @OA\Property(property="id", type="integer", default="999"),
     *        @OA\Property(property="emails", type="arrays", default={"[email protected]", "[email protected]"}),
     *        examples = {
     *          @OA\Examples(example="範例", summary="多個 emails", value={"id": "999", "emails": {"[email protected]", "[email protected]"}}),
     *        }
     *     )
     *   ),
  1. 有些 Api 會設定特定 token 才可有權限取得其資料,其設定可谷歌搜尋一下,也可參照這篇
  • l5-swagger.php 須設定 bear_token
l5-swagger.php
  • php
1
2
3
4
5
6
'bearer_token' => [ // Unique name of security
    'type' => 'apiKey', // Valid values are "basic", "apiKey" or "oauth2".
    'description' => 'Enter token in format (Bearer <token>)',
    'name' => 'Authorization', // The name of the header or query parameter to be used.
    'in' => 'header', // The location of the API key. Valid values are "query" or "header".
],
  • 在 Controller 使用前設定
Promotion.php
  • php
1
2
3
4
5
6
7
8
/**
 * @OAS\SecurityScheme(
 *      securityScheme="bearer_token",
 *      type="http",
 *      scheme="bearer"
 * )
 */
clas PromotionController extends Controller
  • 每個 Api docs 設定 security security={{"bearer_token":{}}},

Swagger 示範寫法

Get 寫法

以下是 Get 寫法,搭配 summary 介紹,可以知道這隻 Api 拿到商品資訊。

PromotionController.php
  • php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
    /**
     * @OA\Get(
     *   tags={"Products"},
     *   path="/your/api/path/{id}",
     *   summary="拿到商品相關資訊",
     *   @OA\Parameter(parameter="productId",in="path",name="productId",required=true,description="input your productID"),
     *   @OA\Response(response=200, description="OK",
     *      content={
     *          @OA\MediaType(
     *              mediaType="application/json",
     *                 example={
     *                    {
     *                      "code": 200,
     *                      "message": "",
     *                      "data": {
     *                         "id": 20,
     *                         "image_url": "https://url.com.tw/your_image_path",
     *                         "name": "your-product-name",
     *                         "price": 333
     *                      }
     *                   }
     *              }
     *          )
     *       }
     *   ),
     *   @OA\Response(response=204, description="Product information is null")
     * )
     */

Create 寫法

筆者以 Create Promotion 為例,去撰寫以下 Swagger。

PromotionController.php
  • php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
    /**
     * @OA\POST(
     *   tags={"Promotions"},
     *   path="/your/api/path/promotion",
     *   summary="create promotion",
     *   security={{"bearer_token":{}}},
     *   @OA\RequestBody(
     *      @OA\MediaType(
     *          mediaType="multipart/form-data",
     *          @OA\Schema(ref="#/components/schemas/Promotion")
     *     )
     *   ),
     *   @OA\Response(response=201, description="Created successfully",
     *      content={
     *          @OA\MediaType(
     *              mediaType="application/json",
     *                  example={
     *                    {
     *                      "code": 201,
     *                      "message": "",
     *                      "data": {
     *                         "update_type": "define-on-you",
     *                         "outcome": true
     *                      }
     *                   }
     *              }
     *          ),
     *      }
     *   ),
     *   @OA\Response(response=401, description="Unauthorized"),
     * )
     */
    public function createPromotion(Request $request)
    {
    }

Update(Patch) 寫法

這邊我示範三個範例,第一個範例是應用 Schema 寫法,筆者只需要在 Model 定義好每個欄位型態與屬性即可,後續引用 @OA\Schema(ref="#/components/schemas/Promotion"),即可應用 Promotion 定義的欄位。

PromotionController.php
  • php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
    /**
     * @OA\POST(
     *   tags={"Promotions"},
     *   path="/your/api/path/{promotionId}",
     *   summary="update promotion by promotionID",
     *   @OA\Parameter(parameter="promotionId",in="path",name="promotionId",required=true,description="input your promotionId",
     *     @OA\Schema(type="integer")
     *   ),
     *   security={{"bearer_token":{}}},
     *   @OA\RequestBody(
     *      @OA\MediaType(
     *          mediaType="multipart/form-data",
     *          @OA\Schema(ref="#/components/schemas/Promotion")
     *     )
     *   ),
     *   @OA\Response(response=204, description="Updated successfully",
     *      content={
     *          @OA\MediaType(
     *              mediaType="application/json",
     *                  example={
     *                    {
     *                      "code": 204,
     *                      "message": "",
     *                      "data": {
     *                         "update_type": "type",
     *                         "outcome": true
     *                      }
     *                   }
     *              }
     *          )
     *       }
     *   ),
     *   @OA\Response(response=401, description="Unauthorized"),
     * )
     */
    public function updatePromotion(Request $request, int $promotionId)
    {
    }

範例二,情境可能應用於這隻 Api,其回傳資料格式與 Model 相差甚大,需要自定義格式輸出。

PromotionController.php
  • php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
    /**
     * @OA\POST(
     *   tags={"Promotions"},
     *   path="/your/api/path/{promotionId}",
     *   summary="update promotion by promotionID",
     *   @OA\Parameter(parameter="promotionId",in="path",name="promotionId",required=true,description="input your promotionId",
     *     @OA\Schema(type="integer")
     *   ),
     *   security={{"bearer_token":{}}},
     *   @OA\RequestBody(
     *     @OA\MediaType(
     *       mediaType="multipart/form-data",
     *       @OA\Schema(
     *         @OA\Property(
     *           property="name",
     *           type="string",
     *         ),
     *         @OA\Property(
     *           property="your-property",
     *           type="string",
     *         ),
     *         @OA\Property(
     *           property="your-property",
     *           type="string",
     *         ),
     *         @OA\Property(
     *           property="your-property",
     *           type="string",
     *         ),
     *       )
     *     )
     *   ),
     *   @OA\Response(response=204, description="Updated",
     *      content={
     *          @OA\MediaType(
     *              mediaType="application/json",
     *                 example={
     *                    {
     *                      "code": 204,
     *                      "message": "",
     *                      "data": {
     *                         "update_type": "depend-on-what-you-define",
     *                         "outcome": true
     *                      }
     *                   }
     *              }
     *          )
     *       }
     *   ),
     *   @OA\Response(response=401, description="Unauthorized"),
     * )
     */
     public function updatePromotion(Request $request, int $promotionId)
     {
     }

第三個示範,是發生在不是傳統的 CRUD,可能是內/外部系統帶上不同的 Request,要做不同的事,實務上經常發生,提供給讀者更多不同寫法。
以下就是這隻 Api 處理單筆與處理多筆更新,接受不同的 payload 格式,再請開發者或測試人員依照格式去發送 Request,Api 才能正常執行。

PromotionController.php
  • php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
    /**
     * @OA\POST(
     *   tags={"Promotions"},
     *   path="/your/api/path/update_depend_on_request",
     *   summary="根據不同參數更新資料",
     *   security={{"bearer_token":{}}},
     *   @OA\RequestBody(
     *      @OA\JsonContent(
     *        type="object",
     *        @OA\Property(property="some-property-type", type="string", default="單筆更新/多筆更新"),
     *        @OA\Property(property="payload", type="json", default="{}"),
     *        examples = {
     *          @OA\Examples(example="單筆更新", summary="單筆", value={"event": "aaa", "payload": {"id": 5566, "name": "王小明", "grade": 2, "timestamp": 1657777068}}),
     *          @OA\Examples(example="多筆更新", summary="多筆", value={"event": "bbb", "payload": {"rows": {{"id": 5566, "name": 王小明, "grade": 2}, {"id": 1234, "name": "王大明", "grade": 5}}, "timestamp": 1657777068}})
     *        }
     *     )
     *   ),
     *   @OA\Response(response=401, description="Unauthorized"),
     *   @OA\Response(response=200, description="成功",
     *      content={
     *          @OA\MediaType(
     *              mediaType="application/json",
     *                  example=
     *                    {
     *                      "code": 200,
     *                      "message": "",
     *                      "data": {}
     *                   }
     *          )
     *       }
     *   ),
     *   @OA\Response(response=422, description="驗證失敗",
     *      content={
     *          @OA\MediaType(
     *              mediaType="application/json",
     *                  example=
     *                    {
     *                      "code": 422,
     *                      "message": {
     *                          "payload.id": {
     *                              "payload.id 須為 integer"
     *                          }
     *                      },
     *                      "data": {}
     *                   }
     *          )
     *       }
     *   ),
     *   @OA\Response(response=400, description="其他失敗",
     *      content={
     *          @OA\MediaType(
     *              mediaType="application/json",
     *                  example=
     *                    {
     *                      "code": 400,
     *                      "message": "商業邏輯處理錯誤",
     *                      "data": {}
     *                   }
     *          )
     *       }
     *   ),
     * )
     */

學習重點

老實說,網路上 Swagger 撰寫語法比較雜亂,需要東拼西湊,才有辦法實現某種特定功能,特別是 Laravel 內的寫法,筆者在撰寫過程中,也是常常踩雷,去嘗試不同的語法,去實現自己想要的結果。
中文資源的 Swagger 教學比較少,筆者想透過自己撰寫的學習經驗,去補充網路上這塊資源的不足,算得上是貢獻自己的微薄之力!
希望看到這篇的讀者,對你有所幫助,藉此整理出自己 Swagger 語法與紀錄自己的學習心得,也算是一大成就!

參考連結