除了上篇介绍的API 文档生成器扩展包之外,我们还可以基于著名的 Swagger 在 Laravel 项目中为 API 接口生成文档。
Swagger 是一个开源的、用于简化 API 开发的工具集,这些工具集涵盖了 API 开发的整个生命周期,从设计到文档、从测试到部署。
在介绍基于 Swagger 工具集生成 API 文档之前,我们有必要先了解下 Swagger 与 OpenAPI 的区别,一句话来概括,就是 OpenAPI 是规范,而 Swagger 是实现这些规范的工具集。
最早,OpenAPI 规范和 Swagger 工具集都属于 Swagger 项目,Swagger 在 2010 年诞生之际,是一个简单的、用于设计 RESTful API 的开源规范,然后为了更好的实现以及可视化规范中定义的 API,又诞生了 Swagger UI、Swagger Editor、Swagger Codegen 等开源工具,随着包含规范和这些开源工具的 Swagger 项目的流行,逐渐创建起了一个庞大的、社区驱动的工具生态系统。
2015 年,Swagger 项目被 SmartBear Software 收购,Swagger 规范被捐赠给 Linux 基金会并被重命名为 OpenAPI 规范(简称 OAS),正式用于标准化 REST API 的描述,同时创建 OpenAPI Initiative 组织以便以开放透明的方式来指导 OpenAPI 的开发(目前该组织包括微软、Google、IBM、以及 SmartBear Software 等30多个成员)。这样一来,Swagger (剥离了规范的 Swagger 工具集)也顺理成章成为了可以在 API 整个开发周期充分 OAS 功能的最流行的工具集。
目前 Swagger 提供的工具集包括以下这些:
Swagger 工具集只是由 OpenAPI 规范原先的创建者开发的工具集,但是并不是唯一实现 OpenAPI 的工具集,还有很多其他的支持该规范的 API 设计、文档、测试、管理、监控实现方案(主要是 OpenAPI 2.0 及上版本),你可以在这里找到它们: OpenAPI 规范实现方案 。
注:OpenAPI 3.0 版本是 Swagger API 规范捐赠给 OpenAPI Initiative 后官方发布的首个版本。后面学院君的介绍和使用也是基于这个最新版本。
在 PHP 中,我们可以基于 swagger-php 扩展包通过 doctrine 注解 为 RESTful API 生成 OpenAPI 文档,当然,在 Laravel 项目也可以使用这个扩展包,不过为了快速集成,我们还可以使用基于该扩展包封装的适用于 Laravel 框架的 L5 Swagger 扩展包 。
在使用 L5 Swagger 扩展包之前,需要通过 Composer 安装它,对于 Laravel 5.8 版本,对应的安装指令如下:
composer require "darkaonline/l5-swagger:5.8.*"
如果是其他 Laravel 版本,对应的安装指令如下:
Laravel | Swagger UI | OpenAPI Spec compatibility | L5-Swagger |
---|---|---|---|
5.7.x | 3 | 3.0, 2.0 | composer require "darkaonline/l5-swagger:5.7.*" |
5.6.x | 3 | 2.0 | composer require "darkaonline/l5-swagger:5.6.*" |
5.5.x | 3 | 2.0 | composer require "darkaonline/l5-swagger:5.5.*" |
5.4.x | 3 | 2.0 | composer require "darkaonline/l5-swagger:5.4.*" |
5.4.x | 2.2 | 1.1, 1.2, 2.0 | composer require "darkaonline/l5-swagger:~4.0" |
5.3.x | 2.2 | 1.1, 1.2, 2.0 | composer require "darkaonline/l5-swagger:~3.0" |
5.2.x | 2.2 | 1.1, 1.2, 2.0 | composer require "darkaonline/l5-swagger:~3.0" |
5.1.x | 2.2 | 1.1, 1.2, 2.0 | composer require "darkaonline/l5-swagger:~3.0" |
安装完成后,使用如下 Artisan 命令发布该扩展包的配置和视图文件:
php artisan vendor:publish --provider "L5Swagger/L5SwaggerServiceProvider"
然后在 .env
中设置 Laravel 项目 API 接口的 URL 根路径:
L5_SWAGGER_CONST_HOST=http://todo.test/dingoapi
取消 config/l5-swagger.php
配置文件中 security.passport
配置项的注释:
// Open API 3.0 support 'passport' => [ // Unique name of security 'type' => 'oauth2', // The type of the security scheme. Valid values are "basic", "apiKey" or "oauth2". 'description' => 'Laravel passport oauth2 security.', 'in' => 'header', 'scheme' => 'http', 'flows' => [ "password" => [ "authorizationUrl" => config('app.url') . '/oauth/authorize', "tokenUrl" => config('app.url') . '/oauth/token', "refreshUrl" => config('app.url') . '/token/refresh', "scopes" => [] ], ], ],
为了生成 OpenAPI 文档,需要在项目代码中添加相应的 OpenAPI 注解,这一点和 Dingo 自带的文档生成功能和 API 文档生成器扩展包的实现原理一样,Swagger 的强大之处在于不仅支持在控制器中添加注解信息,而且支持在模型类甚至是独立的 PHP 文件中添加注解,关于这些注解的用法可以参考 L5 Swagger 扩展包提供的 OpenAPI Annotation 示例 ,以及 Swagger 官网提供的 OpenAPI Specification 文档。
我们以 app/Http/Controllers/Api/TaskController.php
的 index 方法为例,编写第一个 OpenAPI 注解如下:
/** * @OA/Info( * version="3.0", * title="Task Resource OpenApi", * @OA/Contact( * name="学院君", * url="http://xueyuanjun.com", * email="support@todo.test" * ) * ), * @OA/Server( * url="http://todo.test/dingoapi/tasks" * ), * @OA/SecurityScheme( * type="oauth2", * description="Use a global client_id / client_secret and your email / password combo to obtain a token", * name="passport", * in="header", * scheme="http", * securityScheme="passport", * @OA/Flow( * flow="password", * authorizationUrl="/oauth/authorize", * tokenUrl="/oauth/token", * refreshUrl="/oauth/token/refresh", * scopes={} * ) * ) */ class TaskController extends ApiController { public function __construct() { $this->middleware('auth:api'); } /** * @OA/Get( * path="/", * operationId="getTaskList", * tags={"Tasks"}, * summary="Get list of tasks", * description="Returns list of tasks", * @OA/Parameter( * name="Accept", * description="Accept header to specify api version", * required=false, * in="header", * @OA/Schema( * type="string" * ) * ), * @OA/Parameter( * name="page", * description="The page num of the list", * required=false, * in="query", * @OA/Schema( * type="integer" * ) * ), * @OA/Parameter( * name="limit", * description="The item num per page", * required=false, * in="query", * @OA/Schema( * type="integer" * ) * ), * @OA/Response( * response=200, * description="The result of tasks" * ), * security={ * {"passport": {}}, * } * ) */ public function index(Request $request) { $limit = $request->input('limit') ? : 10; // 获取认证用户实例 $user = $request->user('api'); $tasks = Task::where('user_id', $user->id)->paginate($limit); return $this->response->paginator($tasks, new TaskTransformer()); } ... // 其它控制器方法 }
具体注解参数的含义请参考前面给出的 OpenAPI 注解示例及官方文档链接,这样我们就可以通过如下 Artisan 命令基于 Swagger 来生成第一个 OpenAPI 文档了:
php artisan l5-swagger:generate
然后我们在浏览器中通过 http://todo.test/api/documentation
访问 API 文档,就可以看到基于 Swagger UI 渲染的文档信息了:
目前只有一个 API 添加了注解,所以只能看到一个 API,点开这个 API 可以看到通过注解设置的 API 接口请求参数和响应信息:
我们还可以点击右上角那个灰色的认证按钮(锁图标),在弹出的认证表单输入认证信息(通过密码授权的 Passport 认证)进行认证:
认证成功之后,点击「Try it out」按钮即可在当前页面对 API 接口进行测试:
在参数输入框中也可以留空,因为这几个参数都支持默认值,点击「Execute」按钮,即可在页面上看到请求明细及返回的接口数据:
虽然有现成的扩展包,但不得不说,Swagger + OpenAPI 这套方案集成到 Laravel 显然比前面介绍的 API 文档生成方案要复杂,学习成本也更高,但带来的好处也是显而易见的,它可以覆盖 API 开发的完整周期,这里我们仅仅是涉及到文档生成和测试,实际上,我们在开发之初首先可以遵循 OpenAPI 规范来定义 API 文档,然后编写相应的 API 接口,再通过 Swagger 生成对应的 OpenAPI 文档进行预览和本地接口测试,接下来再将这些接口发布到 SwaggerHub 进行团队间的联调和测试,最后将测试通过的接口合并到主干或者发布到线上。
下面我们继续来补全其它方法的 OpenAPI 注解,首先来看 show
方法,在该方法中需要指定一个路由参数,此外,我们还会通过对转化器类添加注解以便在 API 文档中清晰的展示返回数据的结构:
/** * @OA/Get( * path="/{id}", * operationId="getTaskItem", * tags={"Tasks"}, * summary="Get Task", * description="Get specify task by id", * @OA/Parameter( * name="Accept", * description="Accept header to specify api version", * required=false, * in="header", * @OA/Schema( * type="string" * ) * ), * @OA/Parameter( * name="id", * description="The id of the task", * required=true, * in="path", * @OA/Schema( * type="integer" * ) * ), * @OA/Response( * response=200, * description="The task item", * @OA/JsonContent(ref="#/components/schemas/task-transformer") * ), * @OA/Response( * response=404, * description="404 not found" * ), * security={ * {"passport": {}}, * } * ) */ public function show($id) { $task = Task::findOrFail($id); return $this->response->item($task, new TaskTransformer()); }
然后我们在转化器 app/Transformers/TaskTransformer.php
中添加 OpenAPI 注解以便生成文档的时候 Swagger 可以对其进行解析:
/** * @OA/Schema( * schema="task-transformer", * type="object", * title="Task Transformer" * ) */ class TaskTransformer extends TransformerAbstract { /** * The id of the task * @var integer * @OA/Property(format="int64", example=1) */ public $id; /** * The text of the task * @var string * @OA/Property(format="string", example="Test Task") */ public $text; /** * If the task is completed or not * @var string * @OA/Property(format="string", example="yes") */ public $completed; /** * The URL of the task detail page * @var string * @OA/Property(format="string", example="http://todo.test/dingoapi/task/1") */ public $link;
然后我们运行 php artisan l5-swagger:generate
重新生成 API 文档,然后刷新浏览器中的 API 文档页面,就可以看到 Schema 数据:
在 GET /tasks/{id}
API 接口返回的响应数据中,也可以看到对应的关联 Schema:
接下来我们来看创建新任务的 store
方法,该方法对应的路由需要通过 POST 请求来传递请求数据,并且请求数据是通过请求实体传递的,对应的 OpenAPI 注解信息如下:
/** * @OA/Post( * path="/", * operationId="newTaskItem", * tags={"Tasks"}, * summary="Add New Task", * description="create new task", * @OA/Parameter( * name="Accept", * description="Accept header to specify api version", * required=false, * in="header", * @OA/Schema( * type="string" * ) * ), * @OA/RequestBody( * request="text", * required=true, * description="The text of the task", * @OA/Schema( * type="string" * ) * ), * @OA/RequestBody( * request="is_completed", * required=true, * description="If the task is completed or not", * @OA/Schema( * type="boolean" * ) * ), * @OA/Response( * response=200, * description="The task item created", * @OA/JsonContent(ref="#/components/schemas/task-transformer") * ), * security={ * {"passport": {}}, * } * ) */ public function store(CreateTaskRequest $request) { $request->validate([ 'text' => 'required|string' ]); $task = Task::create([ 'text' => $request->post('text'), 'user_id' => auth('api')->user()->id, 'is_completed' => Task::NOT_COMPLETED ]); return $this->response->item($task, new TaskTransformer()); }
除此之外,我们还可以传递 JSON 格式请求数据,此时,我们可以通过 Schema 引用来指定对应的 JSON 对象,以更新任务的 update
方法为例,对应的 OpenAPI 注解信息如下:
/** * @OA/Put( * path="/{id}", * operationId="updateTaskItem", * tags={"Tasks"}, * summary="Update Task", * description="update existed task by id", * @OA/Parameter( * name="Accept", * description="Accept header to specify api version", * required=false, * in="header", * @OA/Schema( * type="string" * ) * ), * @OA/Parameter( * name="id", * description="The id of the task", * required=true, * in="path", * @OA/Schema( * type="integer" * ) * ), * @OA/RequestBody( * request="task_in_body", * required=true, * description="The task to update", * @OA/JsonContent( * ref="#/components/schemas/task-model" * ) * ), * @OA/Response( * response=200, * description="The task item updated", * @OA/JsonContent(ref="#/components/schemas/task-transformer") * ), * @OA/Response( * response=404, * description="404 not found" * ), * security={ * {"passport": {}}, * } * ) */ public function update(Request $request, $id) { $task = Task::findOrFail($id); $updatedTask = tap($task)->update(request()->only(['is_completed', 'text']))->fresh(); return $this->response->item($updatedTask, new TaskTransformer()); }
在该注解中,我们引用了 task-model
Schema 作为传递的 JSON 请求对象,该 Schema 对应的对象是 Task
模型类,我们需要为其添加注解信息如下:
/** * @OA/Schema( * schema="task-model", * type="object", * title="Task model" * ) */ class Task extends Model { /** * The id of the task * @var integer * @OA/Property(format="int64", example=1) */ public $id; /** * The text of the task * @var string * @OA/Property(format="string", example="Test Task") */ public $text; /** * If the task is completed or not * @var string * @OA/Property(format="boolean", example="1") */ public $is_completed; ... }
最后我们为 delete
方法添加 OpenAPI 注解如下:
/** * @OA/Delete( * path="/{id}", * operationId="deleteTaskItem", * tags={"Tasks"}, * summary="Delete Task", * description="delete existed task by id", * @OA/Parameter( * name="Accept", * description="Accept header to specify api version", * required=false, * in="header", * @OA/Schema( * type="string" * ) * ), * @OA/Parameter( * name="id", * description="The id of the task", * required=true, * in="path", * @OA/Schema( * type="integer" * ) * ), * @OA/Response( * response=200, * description="The task is deleted successful" * ), * @OA/Response( * response=404, * description="404 not found" * ), * security={ * {"passport": {}}, * } * ) */ public function destroy($id) { $task = Task::findOrFail($id); $task->delete(); return response()->json(['message' => 'Task deleted'], 200); }
这样我们就简单完成了一个资源控制器类所有方法的 OpenAPI 注解,并且涉及到日常 API 接口的所有常见场景,再次运行 API 文档生成命令,即可在浏览器中查看对应的 API 文档:
好了,关于在 Laravel 项目中集成 Swagger 并生成 API 接口文档我们就简单介绍到这里,更多使用方法请参考 Swagger 扩展包源码以及 Swagger 官方文档。