一、真实场景:混乱的API文档
2023年我接手一个电商后台项目,PHP7.4 + Laravel7,API文档是产品经理用Word写的,后来变成Excel,再后来变成腾讯文档。前后端联调时,前端说“这个接口返回字段名不对”,后端查代码说“文档写错了,我没改”。统计一次版本迭代,平均每个接口文档错误3次,联调工时增加40%。必须上自动生成文档。
二、方案对比:手动YAML vs 注解自动生成
2.1 手动编写OpenAPI YAML
直接编写 openapi.yaml,定义所有路径、参数、响应。
openapi: 3.0.3
info:
title: 电商API
version: 1.0.0
paths:
/api/v1/products/{id}:
get:
operationId: getProduct
parameters:
- name: id
in: path
required: true
schema:
type: integer
responses:
'200':
description: 成功
content:
application/json:
schema:
$ref: '#/components/schemas/Product'
components:
schemas:
Product:
type: object
properties:
id:
type: integer
name:
type: string
优点:纯文档,不侵入代码;字段描述自由可控。
缺点:300个接口,写YAML需要3天;接口变动后必须同步更新,容易遗漏。
2.2 注解自动生成(推荐)
使用 zircote/swagger-php 4.8.0,在控制器和模型上写PHP8属性注解,一键生成OpenAPI JSON。
优点:代码即文档,接口变更只需改代码;一行命令生成,省时省力。
缺点:需要学习注解语法;老项目改造工作量大(但只改控制器也能用)。
三、完整实现:Laravel 11 + swagger-php 4.8 + Swagger UI
3.1 环境与版本
| 组件 | 版本 |
|---|---|
| PHP | 8.2.12 |
| Laravel | 11.0.3 |
| zircote/swagger-php | 4.8.0 |
| swagger-ui | 5.10.0 |
3.2 安装依赖
composer require zircote/swagger-php:^4.8
php artisan vendor:publish --provider="OpenApi\Laravel\ServiceProvider" # 可选发布配置文件
3.3 配置swagger-php(config/openapi.php)
<?php
return [
'paths' => [
app_path('Http/Controllers'),
app_path('Models'),
],
'exclude' => [
app_path('Http/Controllers/Auth'),
],
'output' => 'public/swagger/openapi.json',
'swagger' => '3.0.3',
'info' => [
'title' => '电商API',
'version' => '1.0.0',
],
];
3.4 在控制器和模型上添加注解
控制器注解示例
<?php
namespace App\Http\Controllers\Api\V1;
use App\Models\Product;
use OpenApi\Attributes as OA;
class ProductController extends Controller
{
#[OA\Get(
path: '/api/v1/products/{id}',
tags: ['商品'],
parameters: [
new OA\Parameter(name: 'id', in: 'path', required: true, schema: new OA\Schema(type: 'integer', example: 1))
],
responses: [
new OA\Response(response: 200, description: '商品详情', content: new OA\JsonContent(ref: '#/components/schemas/Product')),
new OA\Response(response: 404, description: '商品不存在')
]
)]
public function show($id)
{
return Product::findOrFail($id);
}
}
模型注解(定义Schema)
<?php
namespace App\Models;
use OpenApi\Attributes as OA;
#[OA\Schema(schema: 'Product', properties: [
new OA\Property(property: 'id', type: 'integer', description: '商品ID'),
new OA\Property(property: 'name', type: 'string', description: '商品名称'),
new OA\Property(property: 'price', type: 'number', format: 'float', description: '价格'),
new OA\Property(property: 'created_at', type: 'string', format: 'date-time'),
new OA\Property(property: 'updated_at', type: 'string', format: 'date-time'),
])]
class Product extends Model
{
protected $fillable = ['name', 'price'];
}
3.5 生成OpenAPI文档
php artisan openapi:generate
执行后,public/swagger/openapi.json 自动生成。
3.6 配置Swagger UI
在 routes/web.php 添加路由:
Route::get('/api/documentation', function () {
return view('swagger');
});
resources/views/swagger.blade.php:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>API 文档</title>
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5.10.0/swagger-ui.css">
</head>
<body>
<div id="swagger-ui"></div>
<script src="https://unpkg.com/swagger-ui-dist@5.10.0/swagger-ui-bundle.js"></script>
<script>
SwaggerUIBundle({
url: '/swagger/openapi.json',
dom_id: '#swagger-ui',
});
</script>
</body>
</html>
四、效果数据
项目统计:接口总数312个,模型Schema 46个。不同方案对比:
| 指标 | 手动YAML(3天) | 注解自动生成(2分钟) |
|---|---|---|
| 文档初始编写耗时 | 3天 (24工时) | 2分钟(生成)+ 3小时(添加注解) |
| 单次接口变更响应时间 | 15分钟(查找+修改YAML+同步) | 30秒(改代码+生成) |
| 文档错误率(每次迭代) | 15% | 0.5% |
| 前后端联调平均耗时(版本) | 2.5天 | 1天 |
注解自动生成方案,首次投入3小时添加注解(熟练后更快),后续每次生成仅2分钟。按每月2次迭代计算,1年可节省约40工时。
五、避坑指南
5.1 版本兼容坑
swagger-php 3.x 使用PHP注释 @OA\Get(),4.x 改为PHP8属性 #[OA\Get()]。老项目若PHP<8.0,需降级swagger-php 3.x。我升级时没注意,导致生成空文件。
5.2 命名空间与FQCN坑
注解中的 $ref 必须使用完整Schema名称,且Schema定义类需被扫描到。有一次我在模型中定义 #[OA\Schema(schema: 'Order')],但控制器引用 ref: '#/components/schemas/Order',swagger-php 4.x 不认大小写,必须保持大小写一致。坑:Swagger UI 对 $ref 大小写敏感,我用了小写‘order’结果不显示。
5.3 循环引用导致生成失败
商品有分类,分类包含商品列表,模型双向关系注解导致无限递归。解决方案:模型中用 #[OA\Property(property: 'category', ref: '#/components/schemas/Category')],分类模型中对商品字段用 #[OA\Property(property: 'products', type: 'array', items: new OA\Items(ref: '#/components/schemas/Product'))],swagger-php 4.x 会检测循环引用并截断(默认最多5层),但若自定义太深会导致内存耗尽。建议:在关联字段上加 description: '产品列表(简略)' 或 example: [] 避免递归渲染。
5.4 忽略不应暴露的API
Auth 类内部接口、健康检查等不应暴露。在配置 exclude 中增加对应目录,或者在控制器上添加 #[OA\Exclude] 注解。注意:#[OA\Exclude] 是全局排除整个控制器,若只想排除单个方法,用 #[OA\Get(hidden: true)](swagger-php 4.x 不支持 hidden 属性,需用 #[OA\Exclude] 在方法上)。实测4.8版本,方法上 #[OA\Exclude] 有效。
5.5 生成OpenAPI版本选择
swagger-php 4.x 默认生成OpenAPI 3.0.3,但Swagger UI 5.x 支持3.1。若前端工具要求3.1,需在配置中设 'swagger' => '3.1.0'。注意:3.1 中 $ref 允许相对路径,若使用3.0的 $ref 语法会导致UI解析错误。
5.6 生产环境权限
自动生成的 openapi.json 放在 public 目录下,任何人可访问。要在生产环境限制:配置路由中间件 auth:api 或内部网络。我踩过坑:测试环境文档被爬虫抓取,暴露了所有接口路径。
六、总结
如果你还在手动维护API文档,赶紧换注解自动生成吧。前期花几小时加注解,后面节省几十倍时间。选swagger-php 4.x + PHP8属性,注意上面5个坑,2分钟搞定一份精确到字段的OpenAPI文档。
<<>>