PHP 中的 OpenAPI/Swagger 文档集成:利用 CI/CD 流水线保证文档与代码一致
大家好,今天我们来聊聊如何在 PHP 项目中集成 OpenAPI/Swagger 文档,并利用 CI/CD 流水线确保文档与代码的一致性。OpenAPI (以前称为 Swagger) 是一种用于描述 RESTful API 的标准规范。它允许开发者和机器理解 API 的功能,而无需访问源代码、文档或网络流量检查。Swagger 工具集可以根据 OpenAPI 规范生成美观的交互式 API 文档,并且可以用于生成客户端代码、服务器 stub 和测试用例。
在项目开发过程中,保持 API 文档与代码的同步更新至关重要。如果文档落后于代码,开发者可能会误解 API 的行为,导致集成问题和错误。手动维护文档既耗时又容易出错。因此,我们需要一种自动化机制来确保文档与代码始终保持一致。这就是 CI/CD 流水线发挥作用的地方。
1. 为什么选择 OpenAPI/Swagger?
在深入探讨集成方法之前,让我们先了解一下使用 OpenAPI/Swagger 的好处:
- 机器可读性: OpenAPI 规范使用 YAML 或 JSON 格式描述 API 接口,这使得机器可以解析和理解 API 的结构和功能。
- 自动文档生成: Swagger UI 可以根据 OpenAPI 规范自动生成美观的交互式 API 文档。开发者可以通过 Swagger UI 轻松地浏览 API 接口、查看请求参数和响应示例,并进行在线测试。
- 代码生成: Swagger Codegen 可以根据 OpenAPI 规范自动生成客户端代码(例如,PHP、Python、Java 等)和服务器 stub。这可以大大减少开发工作量,并确保客户端和服务端之间的接口一致性。
- API 测试: OpenAPI 规范可以用于生成 API 测试用例,从而提高 API 的质量和可靠性。
2. PHP 项目中集成 OpenAPI/Swagger 的方法
在 PHP 项目中集成 OpenAPI/Swagger,通常有以下几种方法:
- 手动编写 OpenAPI 规范: 这是最基本的方法,开发者需要手动编写 YAML 或 JSON 格式的 OpenAPI 规范文件。这种方法需要对 OpenAPI 规范非常熟悉,并且需要花费大量时间来维护文档。
- 使用注释生成 OpenAPI 规范: 许多 PHP 框架(例如,Laravel、Symfony)都提供了基于注释生成 OpenAPI 规范的工具。开发者只需要在代码中添加特定的注释,就可以自动生成 OpenAPI 规范文件。这种方法可以大大减少手动编写文档的工作量。
- 使用代码分析工具生成 OpenAPI 规范: 一些代码分析工具可以自动分析 PHP 代码,并生成 OpenAPI 规范文件。这种方法可以最大限度地减少手动编写文档的工作量,但可能需要对代码进行一些调整才能正确生成文档。
我们将重点讲解使用注释生成 OpenAPI 规范的方法,因为它在 PHP 项目中应用最广泛,并且易于使用和维护。
3. 使用 Swagger-PHP 生成 OpenAPI 规范
Swagger-PHP 是一个 PHP 库,可以根据代码中的注释自动生成 OpenAPI 规范文件。它支持多种注释格式,包括 Swagger、OpenAPI 和 phpDocumentor。
3.1 安装 Swagger-PHP
可以使用 Composer 安装 Swagger-PHP:
composer require zircote/swagger-php
3.2 在代码中添加注释
在 PHP 代码中,可以使用 @OA 前缀的注释来描述 API 接口。例如:
<?php
namespace AppController;
use SymfonyComponentRoutingAnnotationRoute;
use SymfonyComponentHttpFoundationJsonResponse;
/**
* @OAInfo(
* title="My API",
* version="1.0.0",
* description="This is a sample API",
* @OAContact(
* email="[email protected]"
* ),
* @OALicense(
* name="Apache 2.0",
* url="https://www.apache.org/licenses/LICENSE-2.0.html"
* )
* )
*/
class ApiController
{
/**
* @Route("/api/users", methods={"GET"})
* @OAGet(
* path="/api/users",
* summary="Get all users",
* tags={"users"},
* @OAResponse(
* response="200",
* description="Successful operation",
* @OAJsonContent(
* type="array",
* @OAItems(ref="#/components/schemas/User")
* )
* ),
* @OAResponse(
* response="500",
* description="Internal Server Error"
* )
* )
*/
public function getUsers(): JsonResponse
{
// ...
}
/**
* @OAPost(
* path="/api/users",
* summary="Create a new user",
* tags={"users"},
* @OARequestBody(
* required=true,
* @OAJsonContent(ref="#/components/schemas/User")
* ),
* @OAResponse(
* response="201",
* description="User created successfully",
* @OAJsonContent(ref="#/components/schemas/User")
* ),
* @OAResponse(
* response="400",
* description="Bad Request"
* ),
* @OAResponse(
* response="500",
* description="Internal Server Error"
* )
* )
* @Route("/api/users", methods={"POST"})
*/
public function createUser(): JsonResponse
{
// ...
}
}
/**
* @OASchema(
* schema="User",
* type="object",
* @OAProperty(property="id", type="integer", format="int64"),
* @OAProperty(property="name", type="string"),
* @OAProperty(property="email", type="string", format="email")
* )
*/
在上面的代码中,我们使用了以下注释:
@OAInfo: 描述 API 的基本信息,例如标题、版本、描述、联系人和许可证。@OAGet: 描述一个 GET 请求接口。@OAPost: 描述一个 POST 请求接口。@OASchema: 描述一个数据模型。@OAProperty: 描述一个数据模型的属性。@Route(Symfony annotation): Symfony 路由注解,用于定义 API 接口的 URL 和 HTTP 方法。
3.3 生成 OpenAPI 规范文件
可以使用 Swagger-PHP 的命令行工具生成 OpenAPI 规范文件:
./vendor/bin/openapi -o public/openapi.yaml ./src
上面的命令会将 src 目录下的所有 PHP 文件进行扫描,并生成一个名为 openapi.yaml 的 OpenAPI 规范文件,保存到 public 目录下。
3.4 使用 Swagger UI 显示 API 文档
可以使用 Swagger UI 来显示 API 文档。Swagger UI 是一个基于 Web 的工具,可以根据 OpenAPI 规范文件生成美观的交互式 API 文档。
首先,需要下载 Swagger UI 的静态资源文件。可以从 Swagger UI 的官方网站下载:https://swagger.io/tools/swagger-ui/
然后,将 Swagger UI 的静态资源文件放到 public 目录下。
最后,创建一个 HTML 文件,用于加载 Swagger UI 并显示 API 文档:
<!DOCTYPE html>
<html>
<head>
<title>Swagger UI</title>
<link rel="stylesheet" type="text/css" href="./swagger-ui/swagger-ui.css" >
</head>
<body>
<div id="swagger-ui"></div>
<script src="./swagger-ui/swagger-ui-bundle.js"></script>
<script>
window.onload = function() {
const ui = SwaggerUIBundle({
url: "./openapi.yaml",
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIBundle.presets.default
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
})
window.ui = ui
}
</script>
</body>
</html>
将上面的 HTML 文件保存到 public 目录下,例如命名为 swagger.html。
现在,可以在浏览器中访问 http://localhost/swagger.html (根据你的项目配置调整 URL),即可看到 Swagger UI 显示的 API 文档。
4. 利用 CI/CD 流水线保证文档与代码一致
为了确保 API 文档与代码始终保持一致,我们可以将 OpenAPI 规范的生成过程集成到 CI/CD 流水线中。
4.1 CI/CD 流水线配置
以下是一个使用 GitLab CI 的示例:
stages:
- test
- build
- deploy
test:
stage: test
image: php:8.1-cli
script:
- composer install --no-interaction --prefer-dist
- ./vendor/bin/phpunit
build:
stage: build
image: php:8.1-cli
script:
- composer install --no-interaction --prefer-dist
- ./vendor/bin/openapi -o public/openapi.yaml ./src
artifacts:
paths:
- public/openapi.yaml
deploy:
stage: deploy
image: alpine/git
before_script:
- apk add --update openssh-client
- mkdir -p ~/.ssh
- echo "$SSH_PRIVATE_KEY" | tr -d 'r' > ~/.ssh/id_rsa
- chmod 600 ~/.ssh/id_rsa
- ssh-keyscan $DEPLOY_SERVER >> ~/.ssh/known_hosts
script:
- rsync -avz public/openapi.yaml $DEPLOY_USER@$DEPLOY_SERVER:$DEPLOY_PATH
在上面的配置中,我们定义了三个阶段:
- test: 运行单元测试。
- build: 生成 OpenAPI 规范文件。
- deploy: 将 OpenAPI 规范文件部署到服务器。
在 build 阶段,我们使用 Swagger-PHP 的命令行工具生成 OpenAPI 规范文件,并将其保存到 public 目录下。然后,我们使用 artifacts 关键字将 public/openapi.yaml 文件作为构建产物。
在 deploy 阶段,我们使用 rsync 命令将 public/openapi.yaml 文件部署到服务器上的指定目录。需要配置环境变量 SSH_PRIVATE_KEY, DEPLOY_USER, DEPLOY_SERVER 和 DEPLOY_PATH。
4.2 流程说明
- 开发者提交代码到代码仓库。
- CI/CD 流水线自动触发。
- 流水线首先运行单元测试,确保代码质量。
- 然后,流水线生成 OpenAPI 规范文件。
- 最后,流水线将 OpenAPI 规范文件部署到服务器。
通过这种方式,我们可以确保每次代码更新后,API 文档都会自动更新。
5. 其他注意事项
- 注释规范: 在代码中添加注释时,需要遵循一定的规范。例如,可以使用 Swagger-PHP 官方文档中提供的示例作为参考。
- 自动化测试: 除了单元测试之外,还可以添加 API 测试,以确保 API 接口的正确性和稳定性。可以使用工具如 Postman, Insomnia, or PHPUnit with API testing libraries.
- 版本控制: 建议对 OpenAPI 规范文件进行版本控制,以便在需要时可以回滚到之前的版本。将openapi.yaml也放入git仓库进行管理。
- 安全: 确保 Swagger UI 的访问权限受到保护,避免未经授权的访问。 可以通过web服务器的配置来进行控制,如nginx或apache.
- 持续监控: 监控 CI/CD 流水线的运行状态,及时发现和解决问题。
- 代码评审: 代码评审的时候,需要检查API的注释是否完整,正确。
- 环境隔离: 不同的环境(例如,开发环境、测试环境、生产环境)可以使用不同的 OpenAPI 规范文件。可以根据 CI/CD 环境变量来区分。
6. 代码示例
以下是一个更完整的代码示例,展示了如何使用 Swagger-PHP 生成 OpenAPI 规范文件,并使用 Swagger UI 显示 API 文档。
<?php
namespace AppController;
use SymfonyComponentRoutingAnnotationRoute;
use SymfonyComponentHttpFoundationJsonResponse;
use SymfonyComponentHttpFoundationRequest;
/**
* @OAInfo(
* title="User Management API",
* version="1.0.0",
* description="API for managing users",
* @OAContact(
* email="[email protected]"
* ),
* @OALicense(
* name="Apache 2.0",
* url="https://www.apache.org/licenses/LICENSE-2.0.html"
* )
* )
* @OAServer(
* url="http://localhost",
* description="Development server"
* )
* @OATag(
* name="users",
* description="Operations related to users"
* )
*/
class ApiController
{
/**
* @Route("/api/users", methods={"GET"})
* @OAGet(
* path="/api/users",
* summary="Get all users",
* tags={"users"},
* @OAParameter(
* name="limit",
* in="query",
* description="Maximum number of results to return",
* required=false,
* @OASchema(
* type="integer",
* format="int32",
* default=10
* )
* ),
* @OAResponse(
* response="200",
* description="Successful operation",
* @OAJsonContent(
* type="array",
* @OAItems(ref="#/components/schemas/User")
* )
* ),
* @OAResponse(
* response="500",
* description="Internal Server Error"
* )
* )
*/
public function getUsers(Request $request): JsonResponse
{
$limit = $request->query->get('limit', 10);
// ... logic to retrieve users from database
$users = [
[
'id' => 1,
'name' => 'John Doe',
'email' => '[email protected]'
],
[
'id' => 2,
'name' => 'Jane Smith',
'email' => '[email protected]'
]
];
return new JsonResponse($users);
}
/**
* @Route("/api/users", methods={"POST"})
* @OAPost(
* path="/api/users",
* summary="Create a new user",
* tags={"users"},
* @OARequestBody(
* required=true,
* @OAJsonContent(ref="#/components/schemas/User")
* ),
* @OAResponse(
* response="201",
* description="User created successfully",
* @OAJsonContent(ref="#/components/schemas/User")
* ),
* @OAResponse(
* response="400",
* description="Bad Request"
* ),
* @OAResponse(
* response="500",
* description="Internal Server Error"
* )
* )
*/
public function createUser(Request $request): JsonResponse
{
$data = json_decode($request->getContent(), true);
// ... logic to create a new user in the database
$newUser = [
'id' => 3,
'name' => $data['name'],
'email' => $data['email']
];
return new JsonResponse($newUser, 201);
}
/**
* @Route("/api/users/{id}", methods={"GET"})
* @OAGet(
* path="/api/users/{id}",
* summary="Get a user by ID",
* tags={"users"},
* @OAParameter(
* name="id",
* in="path",
* description="User ID",
* required=true,
* @OASchema(
* type="integer",
* format="int64"
* )
* ),
* @OAResponse(
* response=200,
* description="Successful operation",
* @OAJsonContent(ref="#/components/schemas/User")
* ),
* @OAResponse(
* response="404",
* description="User not found"
* ),
* @OAResponse(
* response="500",
* description="Internal Server Error"
* )
* )
*/
public function getUser(int $id): JsonResponse
{
// ... Logic to get user by ID
$user = [
'id' => $id,
'name' => 'Retrieved User',
'email' => '[email protected]'
];
if (!$user) {
return new JsonResponse(['message' => 'User not found'], 404);
}
return new JsonResponse($user);
}
}
/**
* @OASchema(
* schema="User",
* type="object",
* required={"name", "email"},
* @OAProperty(property="id", type="integer", format="int64", readOnly=true),
* @OAProperty(property="name", type="string", example="John Doe"),
* @OAProperty(property="email", type="string", format="email", example="[email protected]")
* )
*/
7. 表格: OpenAPI 注释常用标签
| 注释标签 | 描述 | 示例 |
|---|---|---|
@OAInfo |
定义 API 的基本信息,如标题、版本、描述等。 | @OAInfo(title="My API", version="1.0.0", description="This is a sample API") |
@OAServer |
定义 API 服务器的 URL。 | @OAServer(url="http://localhost", description="Development server") |
@OATag |
定义 API 接口的标签,用于对接口进行分组。 | @OATag(name="users", description="Operations related to users") |
@OAGet |
描述一个 GET 请求接口。 | @OAGet(path="/api/users", summary="Get all users") |
@OAPost |
描述一个 POST 请求接口。 | @OAPost(path="/api/users", summary="Create a new user") |
@OAPut |
描述一个 PUT 请求接口。 | @OAPut(path="/api/users/{id}", summary="Update a user") |
@OADelete |
描述一个 DELETE 请求接口。 | @OADelete(path="/api/users/{id}", summary="Delete a user") |
@OAParameter |
描述一个请求参数。 | @OAParameter(name="id", in="path", description="User ID", required=true) |
@OARequestBody |
描述请求体。 | @OARequestBody(required=true, @OAJsonContent(ref="#/components/schemas/User")) |
@OAResponse |
描述一个响应。 | @OAResponse(response="200", description="Successful operation") |
@OAJsonContent |
描述 JSON 格式的响应内容。 | @OAJsonContent(type="array", @OAItems(ref="#/components/schemas/User")) |
@OASchema |
定义一个数据模型。 | @OASchema(schema="User", type="object", required={"name", "email"}) |
@OAProperty |
描述一个数据模型的属性。 | @OAProperty(property="id", type="integer", format="int64") |
8. 总结:自动化文档生成与持续集成
通过使用 Swagger-PHP 和 CI/CD 流水线,我们可以实现 API 文档的自动化生成和持续集成,从而确保 API 文档与代码始终保持一致,提高开发效率和 API 的质量。 这种方法可以显著减少手动维护文档的工作量,并降低因文档不一致而导致的问题。
9. 概括:文档与代码同步,CI/CD保障质量
利用Swagger-PHP自动生成OpenAPI文档,结合CI/CD流水线,能够保证API文档与代码同步更新。这样的集成方案提高了开发效率,并确保了API的质量和可靠性。