如何编写基于 Swagger-PHP 的 API 文档

从零开始

这一章主要介绍 API 的基本组成部分,包括提供给 API 消费者(所有可能访问 API 的个体,下简称 “消费者”)的的不同 HTTP 请求方法、路径,请求和消息体中的参数,以及返回给消费者的不同 HTTP 状态及响应消息体。

<?php

/**
 * @SWG\Swagger(
 *    swagger="2.0",
 *     schemes={"https"},
 *     host="tcmzapi.emao.com",
 *     basePath="/",
 *     @SWG\Info(
 *         version="1.0.0",
 *         title="淘车猫中转库 Api 文档",
 *         description="淘车猫中转库 Api 文档"
 *     )
 * )
 */

这个文档的内容分成四部分,下面分别来说明。

OpenAPI 规范的版本号

首先我们要通过一个 swagger 属性来声明 OpenAPI 规范的版本。

<?php
/**
 * @SWG\Swagger(
 *     swagger="2.0"
 * )
 */

你没看错,是 swagger,上面已经介绍了,OpenAPI 规范是基于 Swagger 的,在未来的版本中,这个属性可能会换成别的。 目前这个属性的值,暂时只能填写为 2.0

API 描述信息

然后我们需要说明一下 API 文档的相关信息,比如 API 文档版本(注意不同于上面的规范版本)、API 文档名称已经可选的描述信息。

<?php
/**
 * @SWG\Info(
 *     version="1.0.0",
 *     title="淘车猫中转库 Api 文档",
 *     description="淘车猫中转库 Api 文档"
 * )
 */
  • version:API 文档版本
  • title:API 文档标题
  • description:API 文档描述
API 的 URL

作为 web API,一个很重要的信息就是用来给消费者使用的根URL,可以用协议(http 或者 https)、主机名、根路径来描述:

<?php
/**
 * @SWG\Swagger(
 *     schemes={"https"},
 *     host="tcmapi.emao.com",
 *     basePath="/v1"
 * )
 */

这这个例子中,消费者把 https://tcmapi.emao.com/v1 作为根节点来访问各种 API。因为和具体环境有关,不涉及 API 描述的根本内容,所以这部分信息是可选的。

  • schemes:协议,可以是多个
  • host:主机名
  • basePath:根路径

定义一个 API 操作

如果我们要展示一组用户信息,可以这样描述:

<?php
/**
 * @SWG\Swagger(
 *     swagger="2.0"
 *     schemes={"https"},
 *     host="tcmapi.emao.com",
 *     basePath="/v1.0",
 *     @SWG\Info(
 *         version="1.0.0",
 *         title="淘车猫 Api 文档",
 *         description="淘车猫 Api 文档"
 *     )
 * )
 * @SWG\Get(
 *     path="/persons",
 *     summary="获取一些人",
 *     description="返回包含所有人的列表。",
 *     @SWG\Response(
 *         response=200,
 *         description="一个用户列表",
 *         @SWG\Schema(
 *             type="array",
 *             @SWG\Items(
 *                  required={"username"}
 *                  @SWG\Properties()
 *             )
 *         ),
 *     ),
 * )
 */
  • Get:请求的 HTTP 方法,支持 GET/POST/PUT/DELETE 等 HTTP 标准请求方法
  • path:请求的路径
  • summary:接口简介
  • tags:接口标签,可以是多个
  • description:接口描述,支持 Markdown 语法
  • operationId:操作的 ID,需要唯一
添加一个 路径 (path)

我们添加一个 /persons路径,用来访问一组用户信息

在路径中添加一个 HTTP 方法

在每个路径中,我们可以添加任意的 HTTP动词,如 GET/POST/PUT/DELETE 等来操作所需要的资源。

比如需要展示一组用户信息,我们可以在 /person 路径中添加 get 方法,同时还可以填写一些简单的描述信息 (summary) 或者说明该主主法的一段长篇大论 (description)

<?php
/**
 * @SWG\Get(
 *     path="/persons",
 *     summary="获取一些人",
 *     description="返回包含所有人的列表。"
 * )
 */

这样一来,我们调 get https://tcmapi.emao.com/v1/persons 方法就能获取一个用户信息列表了。

定义响应 (response) 类型

对于每个方法(或操作),我们都可以在响应 (responses) 中添加任意的 HTTP状态码(比如 200 OK 或者 404 Not Found 等)。这个例子中我们添加上 200 的响应:

<?php
/**
 * @SWG\Response(
 *     response=200,
 *     description="一个用户列表"
 * )
 */
定义响应内容

get /persons 这个接口返回一组用户信息,我们通过响应消息中的模式(schema)属性来描述清楚具体的返回内容。

一组用户信息就是一个用户信息对象的数组(array),每一个数组元素则是一个用户信息对象(object),该对象包含三个 string 类型的属性:姓氏名字用户名,其中用户名必须提供(required)。

<?php
/**
 * @SWG\Schema(
 *     type="array",
 *     @SWG\Items(
 *          required={"username"},
 *          @SWG\Property(
 *              property="firstName",
 *              type="string",
 *              description="firstName"
 *          ),
 *          @SWG\Property(
 *              property="lastName",
 *              type="string",
 *              description="lastName"
 *          ),
 *          @SWG\Property(
 *              property="username",
 *              type="string",
 *              description="username"
 *          )
 *     )
 * )
 */

定义 请求参数 (query parameteers)

用户太多,我们不想一股脑全部输出出来。这个时候,分页输出是个不错的选择,我们可以通过添加请求参数来实现。

<?php
/**
 * @SWG\Parameter(
 *     name="pageSize",
 *     in="query",
 *     description="Number of persons returned",
 *     type="integer"
 * ),
 * @SWG\Parameter(
 *     name="pageNumber",
 *     in="query",
 *     description="Page number",
 *     type="integer"
 * )
 */
在 get 方法中增加请求参数
<?php
/**
 * @SWG\Get(
 *     path="/persons",
 *     summary="获取一些人",
 *     description="返回包含所有人的列表。",
 *     @SWG\Parameter()
 * )
 */
添加分页参数

在参数列表中,我们添加两个名字(name)分别叫做 pageSizepageNumber 的整型(integer)参数,并作简单描述:

<?php
/**
 * @SWG\Get(
 *     path="/persons",
 *     summary="获取一些人",
 *     description="返回包含所有人的列表。",
 *     @SWG\Parameter(
 *          name="pageSize",
 *          in="query",
 *          description="Number of persons returned",
 *          type="integer"
 *     ),
 *     @SWG\Parameter(
 *          name="pageNumber",
 *          in="query",
 *          description="Page number",
 *          type="integer"
 *     )
 * )
 */

这样一来,消费者就可以通过 get /persons?pageSize=20&pageNumber=2 来访问第 2 页的用户信息(不超过 20 条)了。

定义 路径参数 (path parameter)

有时候我们想要根据用户名来查找用户信息,这时我们需要增加一个接口操作,比如可以添加一个类似 /persons/{username} 的操作来获取用户信息。注意,{username} 是在请求路径中的参数。

<?php
/**
 * @SWG\Get(
 *     path="/persons/{username}",
 *     summary="获取一些人",
 *     description="返回包含所有人的列表。",
 *     @SWG\Parameter(
 *          name="username",
 *          in="path",
 *          required="true"
 *          description="The person's username",
 *          type="string"
 *     )
 * )
 */
添加一个 get /persons/{username} 操作

首先我们在 /persons 路径后面,增加一个 /persons/{username} 的路径,并定义一个 get (操作)方法。

<?php
/**
 * @SWG\Get(
 *     path="/persons/{username}",
 *     summary="Gets a person",
 *     description="Returns a single person for its username"
 * )
 */
定义路径参数 username

因为 {username} 是路径参数,我们需要先像请求参数一样将它添加到 parameters 属性中,注意名称应该同上面大括号( { } ) 里面的名称一致。并通过 in 这个属性,来表示它是一个路径(path)参数。

<?php
/**
 * @SWG\Parameter(
 *     name="username",
 *     in="path",
 *     required="true"
 *     description="The person's username",
 *     type="string"
 * )
 */

定义路径参数时很容易出现的问题就是忘记:required: true,Swagger 的自动完成功能中没有包含这个属性定义。 如果没有写 require 属性,默认值是 false,也就是说 username 参数时可选的。可事实上,作为路径参数,它是必需的。

定义响应消息

别忘了获取单个用户信息也需要填写 200 响应消息,响应消息体的内容就是之前描述过的用户信息(用户信息列表中的一个元素):

<?php
/**
 * @SWG\Response(
 *     response=200,
 *     description="A Person",
 *     @SWG\Schema(
 *         required={"username"},
 *         @SWG\Property(
 *              property="firstName",
 *              type="string"
 *         ),
 *         @SWG\Property(
 *              property="lastName",
 *              type="string"
 *         ),
 *         @SWG\Property(
 *              property="username",
 *              type="string"
 *         )
 *     )
 * )
 */

当然,API 的提供者会对 username 进行校验,如果查无此人,应该返回 404 的异常状态。所以我们再加上 404 状态的响应:

 <?php
 /**
 * @SWG\Response(
 *     response=404,
 *     description="The Person does not exists."
 * )
 */

定义 消息体参数 (body parameter)

当我们需要添加一个用户信息时,我们需要一个能够提供 post /persons 的 API 操作。

<?php
/**
 * @SWG\Post(
 *     path="/persons",
 *     summary="Creates a person",
 *     description="Adds a new person to the persons list.",
 *     @SWG\Parameter(
 *          name="person",
 *          in="body",
 *          required="true"
 *          description="The person to create.",
 *          @SWG\Schema(
 *              required={"username"},
 *              @SWG\Property(
 *                  property="firstName",
 *                  type="string"
 *              ),
 *              @SWG\Property(
 *                   property="lastName",
 *                   type="string"
 *              ),
 *              @SWG\Property(
 *                   property="username",
 *                   type="string"
 *              )
 *          )
 *     ),
 *     @SWG\Response(
 *          response="200",
 *          description="Persons succesfully created."
 *     ),
 *     @SWG\Response(
 *          response="400",
 *          description="Persons couldn't have been created."
 *     )
 * )
 */
添加一个 post /persons 操作

首页在 /persons 路径下添加一个 Post 操作:

<?php
/**
 * @SWG\Post(
 *     path="/persons",
 *     summary="Creates a person",
 *     description="Adds a new person to the persons list.",
 * )
 */
定义消息体参数

接下来我们给 post 方法添加参数,通过 in 属性显式说明参数是在 body 中的。参数的定义参考 get /persons/{username} 的 200 响应消息体参数,也就是包含用户的姓氏、名字、用户名。

<?php
/**
 * @SWG\Parameter(
 *     name="person",
 *     in="body",
 *     required="true"
 *     description="The person to create.",
 *     @SWG\Schema(
 *         required={"username"},
 *         @SWG\Property(
 *             property="firstName",
 *             type="string"
 *         ),
 *         @SWG\Property(
 *              property="lastName",
 *              type="string"
 *         ),
 *         @SWG\Property(
 *              property="username",
 *              type="string"
 *         )
 *     )
 * )
 */
定义响应消息

最后不要忘记定义 post 操作的响应消息。

<?php
/**
 * @SWG\Response(
 *     response="200",
 *     description="Persons succesfully created."
 * ),
 * @SWG\Response(
 *     response="400",
 *     description="Persons couldn't have been created."
 * )
 */

文档瘦身

现在我们已经学会了编写 API 文档的基本方法。不过上面的例子中存在一些重复,这对于程序员的嗅觉来说,就是代码的 “坏味道”。这一章我们一起学习如何通过抽取可重用的定义(definitions)来简化 API 文档。

简化数据模型

我们认真观察第 2 章最后输出的 API 文档,很容易发现 Person 的定义出现了三次,非常的不 DRY☹。

现在,我们通过可重用的 定义 (definition)来重构这个文档:

<?php
/**
 * @SWG\Definition(
 *     definition="Person",
 *     type="object",
 *     required={"username"},
 *     @SWG\Property(
 *         property="firstName",
 *         type="string"
 *     ),
 *     @SWG\Property(
 *         property="lastName",
 *         type="string"
 *     ),
 *     @SWG\Property(
 *         property="username",
 *         type="string"
 *     )
 * )
 */

/**
 * @SWG\Definition(
 *     definition="Persons",
 *     type="array",
 *     @SWG\Items(ref="#/definitions/Person")
 * )
 */

文档简化了很多。这得益于 OpenAPI 规范中关于定义(definition)的章节中允许我们 “一处定义,处处使用”。

添加 定义 (definitions) 项

我们首先在 API 文档的尾部添加一个 定义 (definitions)项(其实它也可以放在文档的任意位置,只不过大家习惯放在文档末尾):

<?php
/**
 * @SWG\Definition()
 */
增加一个可重用的(对象)定义

然后我们增加一个 Person 对象的定义:

<?php
/**
 * @SWG\Definition(
 *     definition="Person",
 *     type="object",
 *     required={"username"},
 *     @SWG\Property(
 *         name="firstName",
 *         type="string"
 *     ),
 *     @SWG\Property(
 *         name="lastName",
 *         type="string"
 *     ),
 *     @SWG\Property(
 *         name="username",
 *         type="string"
 *     )
 * )
 */
引用一个 定义 来增加另一个 定义

在定义项中,我们可以立即引用刚才定义好的 Person 来增加另一个 定义,Persons。Persons 是一个 Person 对象的数组。与之前直接定义的不同之处是,我们增加了一个 引用(reference)属性,也就是 ref 来引用 Person 。

<?php
/**
 * @SWG\Definition(
 *     definition="Persons",
 *     type="array",
 *     @SWG\Items(ref="#/definitions/Person")
 * )
 */
在响应消息中使用 定义

一旦定义好了 Person ,我们可以把原来在响应消息中相应的定义字段替换掉。

get/persons

原来:

<?php
/**
 * @SWG\Response(
 *     response=200,
 *     description="A list of Person",
 *     @SWG\Schema(
 *         type="array",
 *         @SWG\Items(
 *              required={"username"},
 *              @SWG\Property(
 *                  property="firstName",
 *                  type="string"
 *              ),
 *              @SWG\Property(
 *                  property="lastName",
 *                  type="string"
 *              ),
 *              @SWG\Property(
 *                  property="username",
 *                  type="string"
 *              )
 *         )
 *     )
 * )
 */

现在:

<?php
/**
 * @SWG\Response(
 *     response=200,
 *     description="A list of Person",
 *     @SWG\Schema(
 *         type="array",
 *         @SWG\Items(ref="#/definitions/Persons")
 *     )
 * )
 */
get/persons/{username}

原来:

<?php
/**
 * @SWG\Response(
 *     response=200,
 *     description="A Person",
 *     @SWG\Schema(
 *          required={"username"},
 *          @SWG\Property(
 *              property="firstName",
 *              type="string"
 *          ),
 *          @SWG\Property(
 *              property="lastName",
 *              type="string"
 *          ),
 *          @SWG\Property(
 *              property="username",
 *              type="string"
 *          )
 *     )
 * )
 */

现在:

<?php
/**
 * @SWG\Response(
 *     response=200,
 *     description="A Person",
 *     @SWG\Schema(ref="#/definitions/Person")
 * )
 */
在参数中使用 定义

不仅仅在消息中可以使用 定义,在参数中也可以使用。

post /persons

原来:

<?php
/**
 * @SWG\Post(
 *     path="/persons",
 *     summary="Creates a person",
 *     description="Adds a new person to the persons list.",
 *     @SWG\Parameter(
 *          name="person",
 *          in="body",
 *          required="true"
 *          description="The person to create.",
 *          @SWG\Schema(
 *              required={"username"},
 *              @SWG\Property(
 *                  property="firstName",
 *                  type="string"
 *              ),
 *              @SWG\Property(
 *                   property="lastName",
 *                   type="string"
 *              ),
 *              @SWG\Property(
 *                   property="username",
 *                   type="string"
 *              )
 *          )
 *     )
 * )
 */

现在:

<?php
/**
 * @SWG\Post(
 *     path="/persons",
 *     summary="Creates a person",
 *     description="Adds a new person to the persons list.",
 *     @SWG\Parameter(
 *          name="person",
 *          in="body",
 *          required="true"
 *          description="The person to create.",
 *          @SWG\Schema(ref="#/definitions/Person")
 *     )
 * )
 */

简化响应消息

我们看到了 引用 ($ref)的作用,接下来我们再把它用到响应消息的定义中:

定义可重用的 HTTP 500 响应

发生 HTTP 500 错误时,假如我们希望每一个 API 操作都返回一个带有错误码(error code)和描述信息(message)的响应,我们可以这样做:

<?php
/**
 * @SWG\Response(
 *     response="500",
 *     description="An unexpected error occured.",
 *     @SWG\schema(
 *         @SWG\Property(
 *             property="code",
 *             type="string"
 *         ),
 *         @SWG\Property(
 *             property="message",
 *             type="string"
 *         )
 *     )
 * )
 */
增加一个 Error 定义

按照 “一处定义、处处引用” 的原则,我们可以在定义项中增加 Error 的定义:

<?php
/**
 * @SWG\Definition(
 *     definition="Error",
 *     @SWG\Property(
 *         property="code",
 *         type="string"
 *     ),
 *     @SWG\Property(
 *         property="message",
 *         type="string"
 *     )
 * )
 */

而且我们也学会了使用 引用($ref),所以我们可以这样写:

<?php
/**
 * @SWG\Response(
 *     response="500",
 *     description="An unexpected error occured.",
 *     @SWG\schema(ref="#/definitions/Error")
 * )
 */
定义一个可重用的响应消息

上面的文档中,还是有一些重复的内容。我们可以根据 OpenAPI 规范中的 responses 章节的描述,通过定义一个可重用的响应消息,来进一步简化文档。

<?php
/**
 * @SWG\Response(
 *     response="Standard500ErrorResponse",
 *     description="An unexpected error occured.",
 *     @SWG\schema(ref="#/definitions/Error")
 * )
 */

注意:响应消息中引用了 Error 的定义。

使用已定义的响应消息

我们还是通过 引用($ref)来使用一个已经定义好的响应消息,比如:

get /users
<?php
/**
 * @SWG\Response(
 *     response="200",
 *     description="A list of Person",
 *     @SWG\schema(ref="#/definitions/Persons")
 * ),
 * @SWG\Response(
 *     response="500",
 *     ref="#/responses/Standard500ErrorResponse"
 * )
 */

略去一部份不重要的。

深入了解一下

通过前面的练习,我们可以写出一篇结构清晰、内容精炼的 API 文档了。可是 OpenAPI 规范还给我们提供了更多的便利和惊喜,等着我们去了解和掌握。这一章主要介绍用于定义属性和数据模型的高级方法。

私人定制

使用 JSON Schema Draft 4,我们可以定义任意类型的各种属性,举例说明。

定符串 (strings) 长度和格式

当定义个字符串属性时,我们可以定制它的长度及格式:

属性类型描述
minLengthnumber字符串最小长度
maxLengthnumber字符串最大长度
patternstring正则表达式

如果我们规定用户名是长度介于 8~64,而且只能由小写字母和数字来构成,那么我们可以这样写:

<?php
/**
 * @SWG\Property(
 *     property="username",
 *     type="string",
 *     pattern="[a-z0-9]{8,64}",
 *     minLength="8",
 *     maxLength="64"
 * )
 */
日期和时间

日期和时间的处理参考 RFC 3339 ,我们唯一要做的就是写对格式:

格式属性包含内容属性示例
dateISO8601 full-date2016-04-01
dateTimeISO8601 date-time2016-04-16T16:06:05Z

如果我们在 Person 的定义中增加 生日上次登录时间 时间戳,我们可以这样写:

<?php
/**
 * @SWG\Property(
 *     property="dateOfBirth",
 *     type="string",
 *     format="date"
 * ),
 * @SWG\Property(
 *     property="lastTimeOnline",
 *     type="string",
 *     format="dateTime"
 * )
 */
数字类型与范围

当我们定义一个数字类型的属性时,我们可以规定它是一个整型、长型、浮点型或者双浮点型。

名称类型格式
integerintegerint32
longintegerint64
floatnumberfloat
doublenumberdouble

和字符串一样,我们也可以定义数字属性的范围,比如:

属性类型描述
minimumnumber最小值
maximumnumber最大值
exclusiveMinimumboolean数值必须 > 最小值
exclusiveMaximumboolean数值必须 < 最大值
multipleOfnumber数值必须是 multipleOf 的整数倍

如果我们规定 pageSize 必须是整数,必须 > 0 且 <=100,还必须是 10 的整数倍,可以这样写:

<?php
/**
 * @SWG\Parameter(
 *     name="pageSize",
 *     in="query"
 *     description="Number of persons returned",
 *     type="integer",
 *     format="int32",
 *     minimum: 0,
 *     exclusiveMinimum: true,
 *     maximum: 100,
 *     exclusiveMaximum: false,
 *     multipleOf: 10
 * )
 */
枚举类型

我们还可以定义枚举类型,比如定义 Error 时,我们可以这样写:

<?php
/**
 * @SWG\Property(
 *     property="code",
 *     type="string",
 *     enum={"DBERR", "NTERR", "UNERR"}
 * )
 */

code 的值只能从三个枚举值中选择。

数值的大小和唯一性

数字的大小和唯一性通过下面这些属性来定义:

属性类型描述
minItemsnumber数值中的最小元素个数
maxItemnumber数值中的最大元素个数
uniqueItemsboolean标示数组中的元素是否唯一

比如我们定义一个用户数组 Persons,希望返回的用户信息条数介于 10~100 之间,而且不能有重复的用户信息,我们可以这样写:

<?php
/**
 * @SWG\Property(
 *     property="Persons",
 *     @SWG\Items(
 *         type="array",
 *         minItems="10",
 *         maxItems="100",
 *         uniqueItems="true",
 *         ref="#/definitions/Person"
 *     )
 * )
 */
二进制数据

可以用 string 类型来表示二进制数据:

格式属性包含
byteBase64 编码字符
binaryBase64 任意十进制的数据序列字符

比如我们需要在用户信息中增加一个头像属性(avatarBase64PNG)用 base64 编码的 PNG 图片来表示,可以这样写:

<?php
/**
 * @SWG\Property(
 *     property="avatarBase64PNG",
 *     type="string",
 *     format="byte"
 * )
 */

高级数据定义

读写操作同一定义的数据

有时候我们读取资源信息的内容会比我们写入资源信息的内容(属性)更多,这很常见。是不是意味着我们必须专门为读取资源和写入资源分别定义不同的数据模型呢?幸运的是,OpenAPI 规范中提供了 readOnly 字段来帮我们解决整问题。比如:

<?php
/**
 * @SWG\Property(
 *     property="lastTimeOnline",
 *     type="string",
 *     format="dateTime",
 *     readOnly="true"
 * )
 */

上面这个例子中,上次在线时间(lastTimeOnline )是 Person 的一个属性,我们获取用户信息时需要这个属性。但是很明显,在创建用户时,我们不能把这个属性 post 到服务器。于是我们可以把它标记为 readOnly。

组合定义确保一致性

一致性设计是在编写 API 文档时需要重点考虑的问题。比如我们在获取一组用户信息时,需要同时获取页面信息(totalItems, totalPage, pageSize, currentPage)等,而且这些信息 必须 在根节点上。

怎么办呢?首先想到的做法就是:

<?php
/**
 * @SWG\Definition(
 *     definition="PagedPersonsV1",
 *     @SWG\Property(
 *          property="items",
 *          type="array",
 *          @SWG\Items(ref="#/definitions/Person")
 *     ),
 *     @SWG\Property(
 *          property="totalItems",
 *          type="integer"
 *     ),
 *     @SWG\Property(
 *          property="totalPages",
 *          type="integer"
 *     ),
 *     @SWG\Property(
 *          property="pageSize",
 *          type="integer"
 *     ),
 *     @SWG\Property(
 *          property="currentPage",
 *          type="integer"
 *     )
 * )
 */

如果其他 API 操作也需要这些 页面信息,那就意味着这些属性必须一遍又一遍的定义。不仅重复体力劳动,而且还很危险:比如忘记了其中的一两个属性,或者需要添加一个新的属性进来,那就是霰弹式的修改,想想都很悲壮。

稍微好一点的做法,就是根据前面学习的内容,把这几个属性抽取出来,建立一个 Paging 模型,“一处定义、处处使用”:

<?php
/**
 * @SWG\Definition(
 *     definition="PagedPersonsV2",
 *     @SWG\Property(
 *          property="items",
 *          type="array",
 *          @SWG\Items(ref="#/definitions/Person")
 *     ),
 *     @SWG\Property(
 *          property="Paging",
 *          @SWG\Items(ref="#/definitions/Paging")
 *     )
 * ),
 * @SWG\Definition(
 *     definition="Paging",
 *     @SWG\Property(
 *          property="totalItems",
 *          type="integer"
 *     ),
 *     @SWG\Property(
 *          property="totalPages",
 *          type="integer"
 *     ),
 *     @SWG\Property(
 *          property="pageSize",
 *          type="integer"
 *     ),
 *     @SWG\Property(
 *          property="currentPage",
 *          type="integer"
 *     )
 * )
 */

但是,页面属性都不再位于 根节点!与我们前面设定的要求不一样了。怎么破?

JSON Schema v4 property 中定义的 allOf,能帮我们解围:

<?php
/**
 * @SWG\Definition(
 *     definition="PagedPersons",
 *     allOf={
            @SWG\Schema(ref="#/definitions/Persons"),
 *          @SWG\Schema(ref="#/definitions/Paging"),
 *     }
 * )
 */

上面这个例子表示,PagedPersons 根节点下,具有将 Persons 和 Paging 展开 后的全部属性。

allOf 同样可以使用行内的数据定义,比如

<?php
/**
 * @SWG\Definition(
 *     definition="PagedCollectingItems",
 *     allOf={
            @SWG\Property(
 *              property="items",
 *              type="array",
 *              minItems="10",
 *              maxItems="100",
 *              uniqueItems="true",
 *              @SWG\Items(ref="#/definitions/CollectingItem")
 *          ),
 *          @SWG\Schema(ref="#/definitions/Paging"),
 *     }
 * )
 */

输入输出模型

这一章主要介绍如何定义高度精确化的参数和响应消息等。

高级参数定义

必带参数和可选参数

我们已经知道使用关键字 required 来定义一个必带参数。

定义必带参数和可选参数

在一个参数中,required 是一个 boolean 型的可选值。它的默认值是 false 。

比如在某个操作中,username 是必填参数:

<?php
/**
 * @SWG\Parameter(
 *     name="username",
 *     in="path",
 *     required="true"
 *     description="The person's username",
 *     type="string"
 * )
 */
定义必带属性和可选属性

根据定义,required 是一个字符串列表,列表中包含各必带参数名。如果某个参数在这张列表中找不到,那就说明它不是必带参数。如果没有定义 required ,就说明所有参数都是可选。如果 required 定义在一个 HTTP 请求上,这说明所有的请求参数都是必填。

在 POST 、persons 中有 Person 的定义,在这里 username 这个属性是必带的,我们可以指定它为 required ,其他非必带字段则不指定:

<?php
/**
 * @SWG\Items(
 *     required={"username"},
 *     @SWG\Property(
 *         property="firstName",
 *         type="string"
 *     ),
 *     @SWG\Property(
 *         property="lastName",
 *         type="string"
 *     ),
 *     @SWG\Property(
 *         property="username",
 *         type="string"
 *     )
 * )
 */
带默认值的参数

通过关键字 default,我们可以定义一个参数的默认值。当这个参数不可得(请求未带或者服务器未返回)时,这个参数就取默认值。因此设定了某个参数的默认值后,它是否 required 就没意义了。

定义参数的默认值

我们定义参数 pageSize 的默认值为 20 ,那么如果请求时没有填写 pageSize ,服务器也会默认返回 20 个元素。

<?php
/**
 * @SWG\Parameter(
 *     name="pageSize",
 *     in="query"
 *     description="Number of persons returned",
 *     type="integer",
 *     format="int32",
 *     minimum="0",
 *     exclusiveMinimum="true",
 *     maximum="100",
 *     exclusiveMaximum="false",
 *     multipleOf="10",
 *     default="20"
 * )
 */
定义属性的默认值

同参数,使用关键字 default 即可。

带空值的参数

在 GET /persons 时,如果我们想添加一个参数来过滤 “是否通过实名认证” 的用户,应该怎么做呢?首先想到的是这样:GET /persons?page=2&includeVerifiedUsers=true ,问题是 includeVerifiedUsers 语义已经如此清晰,而让 “=true” 显得很多余。我们能不能直接用:GET /persons?page=2&includeVerifiedUsers 呢?

要做到这种写法,我们需要一个关键字 allowEmptyValue 。我们定义 includeVerifiedUsers 时允许它为空。那么如果我们请求 GET /persons?page=2&includeVerifiedUsers 则表示需要过滤 “实名认证” 用户,如果我们直接请求 GET /persons?page=2 则表示不过滤:

<?php
/**
 * @SWG\Parameter(
 *     name="includeNonVerifiedUsers",
 *     in="query"
 *     type="boolean",
 *     default="false",
 *     allowEmptyValue="true"
 * )
 */
参数组

设计 API 的时候,我们经常会遇到在 GET 请求中需要携带一组请求参数的情况。如何在 API 文档章呈现呢?很简单,我们只需要设定 参数类型(type)array,并选择合适的 组合格式(collectionFormat) 就行了。

组合格式描述
csv (default value)Comma separated values(逗号分隔) foo,bar
ssvSpace separated values(空格分隔) foo bar
tsvTab separated values(反斜杠分隔) foo\bar
pipesPipes separated values(竖线分隔) foo|bar
multi单属性可以取多个值,比如 foo=bar&foo=baz. 只适用于查询参数和表单参数。

比如我们想根据多种参数(username , firstname , lastname , lastTimeOnline )等来对 Person 进行带排序的查询。我们需要一个这样的 API 请求: GET /persons?sort=-lastTimeOnline|+firtname|+lastname 。用于排序的参数是 sort ,+ 表示升序,- 表示降序。

相应的 API 文档,可以这样写:

<?php
/**
 * @SWG\Parameter(
 *     name="sort",
 *     in="query"
 *     type="array",
 *     uniqueItems=true,
 *     minItems=1,
 *     maxItems=3,
 *     collectionFormat="pipes",
 *     @SWG\items(
 *         type="string",
 *         pattern="[-+](username|lastTimeOnline|firstName|lastName)",
 *     )
 * )
 */

现在我们就能搞定 GET /persons?sort=-lastTimeOnline|+firtname|+lastname 这种请求了。当然,我们还可以指定排序的默认值,锦上添花。

<?php
/**
 * @SWG\Parameter(
 *     name="sort",
 *     in="query"
 *     type="array",
 *     uniqueItems=true,
 *     minItems=1,
 *     maxItems=3,
 *     collectionFormat="pipes",
 *     @SWG\items(
 *         type="string",
 *         pattern="[-+](username|lastTimeOnline|firstName|lastName)",
 *     ),
 *     default={"-lastTimeOnline", "+username"}
 * )
 */
消息头 (Header) 参数

参数,按照位置来分,不仅仅包含路径参数、请求参数和消息体参数,还包括消息头参数和表单参数等。比如我们可以在 HTTP 请求的消息头上加一个 User-Agent (用于跟踪、调试或者其他),可以这样定义它:

<?php
/**
 * @SWG\Parameter(
 *      parameter="userAgent",
 *      name="User-Agent",
 *      type="string",
 *      in="header",
 *      required=true
 *  )
 */

然后像使用其他参数一样使用它:

<?php
/**
 * @SWG\Parameter(ref="#/parameters/userAgent")
 */
表单参数

有些 js-less-browser 的老浏览器不支持 POST JSON 数据,比如在创建用户时,只能够以这样个格式请求:

POST /js-less-persons

username=apihandyman&firstname=API&lastname=Handyman

没有问题,丝袜哥可以搞定。我们只需要把各个属性的 in 关键字定义为 formData ,然后设置 consumes 的媒体类型为 application/x-www-form-urlencoded 即可。

<?php
/**
 * @SWG\Post(
 *     path="/persons",
 *     summary="Creates a person",
 *     description="For JS-less partners",
 *     consumes={"application/x-www-form-urlencoded"},
 *     produces={"text/html"},
 *     @SWG\parameter(
 *         name="username",
 *         in="formData",
 *         required="true",
 *         pattern="[a-z0-9]{8,64}",
 *         minLength=8,
 *         maxLength=64,
 *         type="string"
 *
 *      )
 * )
 */
文件参数

当我们要处理一个请求,输入类型是 文件 时,我们需要:

  • 使用 multipart/form-data 媒体类型;

  • 设置参数的 in 关键字为 formData;

  • 设置参数的 类型(type) 为 file。

比如:

<?php
/**
 * @SWG\Post(
 *     path="/images",
 *     summary="Uploads an image",
 *     consumes={"multipart/form-data"},
 *     @SWG\Parameter(
 *         name="image",
 *         in="formData",
 *         type="file"
 *     )
 *     @SWG\Response(
 *         response="200",
 *         description="Image's ID",
 *         @SWG\Schema(
 *             @SWG\Property(
 *                  property="imageId",
 *                  type="string"
 *             )
 *         )
 *     )
 * )
 */

有时候我们想限定输入文件的类型(后缀),很不幸的是,根据现在 V2.0 的规范暂时还做不到☹

参数的媒体类型

一个 API 可以消费各种不同的媒体类型,比如说最常见的是 application/json 类型的数据,当然这不是 API 唯一支持的类型。我们可以在 文档的根节点 或者 一个操作的根节点 下添加关键字 consumes,来定义这个操作能够消费的媒体类型。

比如我们的 API 全部都接受 JSON 和 YAML 的数据,那我们可以在文档的根节点下添加:

<?php
/**
 * @SWG\Swagger(
 *     consumes={"application/json", "application/x-yaml"}
 * )
 */

如果某个操作(比如上传图片的操作)很特殊,它可以通过自己添加 consumes 来覆盖全局设置:

<?php
/**
 * @SWG\Post(
 *     path="/images",
 *     summary="Uploads an image",
 *     consumes={"multipart/form-data"},
 *     @SWG\Parameter(
 *         name="image",
 *         in="formData",
 *         type="file"
 *     )
 *     @SWG\Response(
 *         response="200",
 *         description="Image's ID",
 *         @SWG\Schema(
 *             @SWG\Property(
 *                  name="imageId",
 *                  type="string"
 *             )
 *         )
 *     )
 * )
 */

高级响应消息定义

不带消息体的响应消息

不带消息体的响应很常见,比如 HTTP 204 状态响应本身就表示服务器返回不带任何消息内容的成功消息。

要定义一个不带消息体的响应很简单,我们只需要写响应状态和描述就行了:

<?php
/**
 * @SWG\Response(
 *     response="204",
 *     description="Person succesfully created."
 * )
 */
响应消息中的必带参数和可选参数

与请求消息中类似,我们使用 required 参数来表示,比如请求一个用户信息时, 服务器必须返回 username。

响应消息头

API 的返回结果不仅仅体现下 HTTP 状态和响应消息体,还可以在响应消息头上做文章。比如我们可以限定一个 API 的使用次数和使用时间段,在响应消息头中,增加一个属性 X-Rate-Limit-Remaining 来表示 API 可调用的剩余次数,增加另一个属性 X-Rate-Limit-Reset 来表示 API 的有效截止时间。

<?php
/**
 * @SWG\Response(
 *     response="204",
 *     description="Person succesfully created."
 *     @SWG\Header(
 *       header="X-Rate-Limit-Remaining",
 *       type="integer",
 *       format="int32"
 *     ),
 *     @SWG\Header(
 *       header="X-Rate-Limit-Reset",
 *       type="string",
 *       format="date-time"
 *     )
 * )
 */
默认响应消息

我们在定义响应消息时,通常会列举不同的 HTTP 状态结果。如果有些状态不在我们 API 文档的定义范围(比如服务器需要返回 993 的状态),该怎么处理呢?这时需要通过关键字 default 来定义一个默认响应消息,用于各种 定义之外 的状态响应,比如:

<?php
/**
 * @SWG\Response(
 *     response="default",
 *     description="default response"
 * )
 */

目前这个配置也不支持 “一次定义,处处使用” 。☹

响应消息的媒体类型

与请求消息一样,我们也可以定义响应消息所支持的媒体类型,不同的是我们要用到关键字 produces(与请求消息中的 consumes 相对,由此可见,API 文档描述的主体是服务提供者)。

比如,我们可以在文档的根路径下全局设置:

<?php
/**
 * @SWG\Swagger(
 *     produces={"application/json", "application/x-yaml"}
 * )
 */

也可以在某个操作的根路径下覆盖设置。

定义某个参数只存在于响应消息中

如前章节 4.2.1 中已经提到的,定义一个对象,其中某个属性我们只希望在响应消息中携带,而不希望在请求消息中携带,应该用 readOnly 关键字来表示

让文档的可读性更好

分类标签 (Tags)

通过关键字 tags 我们可以对文档中接口进行归类,tags 的本质是一个字符串列表。tags 定义在文档的根路径下。

单标签

比如说 GET /persons 属于用户(Person) 这个分类的,那么我们可以给它贴个标签:

<?php
/**
 * @SWG\Get(
 *     path="/persons",
 *     summary="Gets some persons",
 *     description="Returns a list containing all persons. The list supports paging.",
 *     operationId="searchUsers",
 *     @SWG\Parameter(ref="#/parameters/userAgent"),
 *     tags={"Persons"}
 * )
 */
多标签

一个操作也可以同时贴几个标签,比如:

<?php
/**
 * @SWG\Get(
 *     path="/js-less-consumer-persons",
 *     summary="Creates a person",
 *     description="For JS-less partners",
 *     operationId="createUserJS",
 *     @SWG\Parameter(ref="#/parameters/userAgent"),
 *     deprecated=true
 *     tags={"JSLess", "Persons"}
 * )
 */

贴上标签后,在 Swagger Editor 和 Swagger UI 中能够自动归类,我们可以按照标签来筛选接口,试试吧?

无处不在的描述文字(Descriptions)

description 这个属性几乎是无处不在,为了提高文档的可读性,我们应该在必要的地方都加上描述文字。

安全项的描述
模式 (Schema) 的描述

每一种模式(Schema),都会有一个标题(title)和一段描述,比如:

<?php
/**
 * @SWG\Definition(
 *     definition="Person",
 *     title="Human",
 *     description="A person which can be the user itself or one of his friend"
 * )
 */
属性的描述

比如:

<?php
/**
 * @SWG\Property(
 *     property="firstName",
 *     description="first name",
 *     type="string"
 * )
 */
参数的描述

比如:

/**
 * @SWG\Parameter(
 *      parameter="username",
 *      name="username",
 *      type="string",
 *      in="path",
 *      required=true,
 *     description="The person's username"
 *  )
 */
操作的概述 (summary)、描述和操作 ID (operationId)

一个操作(Operation)通常都会包含概述和描述信息。而且我们还可以添加一个关键字 operationId,这个关键字通常用来指示服务提供者对这个操作的 处理函数 的函数名。比如:

<?php
/**
 * @SWG\Get(
 *     path="/persons",
 *     summary="Gets some persons",
 *     description="Returns a list containing all persons. The list supports paging.",
 *     operationId="searchUsers",
 *     @SWG\Parameter(ref="#/parameters/userAgent"),
 *     tags={"Persons"}
 * )
 */
响应消息的描述
<?php
/**
 * @SWG\Response(
 *     response=200,
 *     description="A list of Person"
 * )
 */
响应消息头的描述
标签的描述

我们在 API 文档的根路径下添加了 tags 的定义,对于其中的每一个标签,我们都可以添加描述信息:

<?php
/**
 * @SWG\Tag(
 *     names="Persons",
 *     description="Everything you need to handle users and friends"
 * )
 */

在描述中使用 Markdown 语法

在绝大部份的 description 中,我们可以使用 GFM (Github Flavored Markdown)语法。

多行描述

使用符号 | 然后在新行中打一个 tab(注意:YAML 的 tab 是两个空格 ),就可以编辑多行描述,比如:

<?php
/**
 * @SWG\Tag(
 *     names="Persons",
 *     description="|
 *   Returns a list containing all items this person is looking for.
The list supports paging.
 *     "
 * )
 */

示例数据 (Examples)

我们已经知道了用 Schema 来描述参数和属性,有的时候,用示例数据更有表现了。我们可以使用关键字 example 来给原子属性或者对象添加示例数据。

原子属性的示例数据
<?php
/**
 * @SWG\Property(
 *     property="firstName",
 *     description="first name",
 *     type="string",
 *     example="John"
 * )
 */
示例数据的优先级

如果我们在各个级别(比如参数、对象、定义、响应消息)都添加了示例数据。支持 OpenAPI 规范的各解析工具 都是最高级别 的定义为准。

标记为弃用

我们可以通过关键字 deprecated 置为 true 来标记接口的 弃用 状态,比如:

<?php
/**
 * @SWG\Get(
 *     path="/js-less-consumer-persons",
 *     summary="Creates a person",
 *     description="For JS-less partners",
 *     operationId="createUserJS",
 *     @SWG\Parameter(ref="#/parameters/userAgent"),
 *     deprecated=true
 *     tags={"JSLess", "Persons"}
 * )
 */

链接到外部文档

一般来说,项目中不光只有一篇 API 文档,还应该有些描述 application key,测试用例,操作链以及其他内容的文档,这些文档一般是单独成篇的。如果在描述某个接口时,我们想链接这些文档,可以通过关键字 externalDoc 来添加,例如:

<?php
/**
 * @SWG\ExternalDocumentation(
 *     description="Complete documentation describing how to use this API",
 *     url="http://doc.simple.api/"
 * )
 */
  • 18
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值