)
前言先说一个术语上的纠正因为「用 Swagger 生成接口文档」这句话里藏着一个常见的混淆点Swagger 现在是一套工具集的名字真正描述接口结构的规范叫 OpenAPI SpecificationOAS。Swagger 规范在 2015 年由 SmartBear 捐给 Linux 基金会下的 OpenAPI Initiative2.0 之后更名为 OpenAPI今天的「Swagger」通常指 Swagger UI渲染文档的网页、Swagger Editor在线编辑器这些工具。所以你写的是 OpenAPI 文档用 Swagger UI 来展示它。文档里的字段名、版本号3.0.x / 3.1.x都以 OpenAPI 规范为准不要按印象编。第二个误解是「装个包跑一条命令文档就自动出来了」。真实情况是Laravel 生态里的方案分两类一类依赖注解/属性你要在控制器上写#[OA\...]或写 docblock 注解工具负责收集并生成 JSON另一类靠扫描代码推断读取路由、FormRequest 校验规则自动拼出结构。本文讲的是前者的主流实现 L5-Swagger因为它和 OpenAPI 规范贴合最紧、可迁移性最好文末会简单对比另一类方案方便你按项目情况选。本文覆盖三件事OpenAPI 与 Swagger 的关系、L5-Swagger 的环境搭建含版本前提、以及注解与 PHP 8 属性两种写法的最小可用示例。一、基本概念一份文档由哪几块组成一份 OpenAPI 3.0 文档通常是一个 JSON 或 YAML 文件的顶层结构是固定的理解这几块写注解时就不会「不知道该往哪写」顶层字段含义在注解里对应openapi规范版本号如3.0.3由工具自动写入info标题、版本、描述、许可证OA\Infoservers接口的基础地址列表OA\Serverpaths每个路径 每个 HTTP 方法一个条目OA\Get/OA\Post等components可复用的 schema、参数、响应定义OA\Schema等在paths之下一个接口条目的内容基本是四样summary/description说明、parameters路径、查询、请求头参数、requestBody请求体、responses响应其中响应要按状态码分别列。Swagger UI 就是拿这份结构渲染成可折叠、可试调的页面。一个关于术语的重要区分文档生成写文档和文档渲染看文档是两件事。L5-Swagger 既是 swagger-php 的 Laravel 封装生成也内置了 Swagger UI 的页面渲染。很多「装了不生效」的问题其实是生成成功但渲染路由没配对。二、环境搭建前提条件先确认清楚swagger-php4.x 支持 OpenAPI 3.0 并支持 PHP 8 属性写法swagger-php3.x 只有 docblock 注解写法。L5-Swagger 8.x 面向 Laravel 9/10/11需要 PHP 8.1。实际能装到哪个版本以composer require后composer show的输出为准不要照抄网上的版本号。# 在 Laravel 项目根目录执行composer require darkaonline/l5-swagger# 发布配置文件Laravel 8 及以后支持 provider 的短类名php artisan vendor:publish --provider L5Swagger\L5SwaggerServiceProvider发布后会多出config/l5-swagger.php。这个文件里有几个键值得先看一眼配置键作用documentations.default.routes.api文档页面的访问路径默认api/documentationdocumentations.default.paths.docs生成的 JSON 存放目录默认storage/api-docsdocumentations.default.paths.annotations扫描注解的目录默认appdocumentations.default.generate_always是否每次请求都重新生成documentations.default.middleware文档页面上挂的中间件再往.env里补两项# .envL5_SWAGGER_GENERATE_ALWAYStrueL5_SWAGGER_CONST_HOSThttp://localhost:8000L5_SWAGGER_GENERATE_ALWAYStrue只在开发环境开。它的语义是「每次访问文档页面都重新扫描代码」方便边改边看生产环境开着会带来可观的额外开销应当关掉、改为在部署流程里显式执行生成命令。生成文档并访问# 手动生成一次产物在 storage/api-docs/api-docs.jsonphp artisan l5-swagger:generate# 启动开发服务器后访问文档页面php artisan serve# 浏览器打开 http://127.0.0.1:8000/api/documentation如果是 Laravel 11 项目且还没有 API 路由文件先执行php artisan install:api它会补上routes/api.php并装好 Sanctum。缺这个文件时你写好的注解会被生成但接口本身不存在文档里自然也是空的。三、写注解PHP 8 属性写法与 docblock 写法先在控制器上写全局信息。OA\Info这类「文档级」对象只写一次即可放在任意一个会被扫描到的类上都行。?php // 需要 PHP 8.0 与 swagger-php 4.xLaravel 9 及以上namespace App\Http\Controllers;use App\Models\User;use Illuminate\Http\JsonResponse;use OpenApi\Attributes as OA;#[OA\Info(version: 1.0.0, title: 商城 API, description: 对外提供的商品与用户接口)]#[OA\Server(url: http://localhost:8000, description: 本地开发环境)]class UserController extends Controller{#[OA\Get(path: /api/users/{id},summary: 获取单个用户,tags: [User],parameters: [new OA\Parameter(name: id,in: path,required: true,description: 用户 ID,schema: new OA\Schema(type: integer)),],responses: [new OA\Response(response: 200,description: 成功,content: new OA\JsonContent(type: object,properties: [new OA\Property(property: id, type: integer, example: 1),new OA\Property(property: name, type: string, example: alice),])),new OA\Response(response: 404, description: 用户不存在),])]public function show(int $id): JsonResponse{return response()-json(User::query()-findOrFail($id));}}几个容易写错的点OA\Parameter的in取值是path/query/header/cookie之一路径参数必须required: trueOpenAPI 规范要求路径参数一定必填responses里response是状态码可以是整数 200 也可以是字符串2XXOA\JsonContent与OA\Schema都能描述结构区别是前者用于 content 上下文。PHP 7 环境只能用 docblock 注解写法如下swagger-php3.x 风格?php // 适用于 PHP 7.x swagger-php 3.x需在 composer.json 的 extra 里开启注解支持/*** OA\Get(* path/api/users/{id},* summary获取单个用户,* tags{User},* OA\Parameter(nameid, inpath, requiredtrue, OA\Schema(typeinteger)),* OA\Response(response200, description成功)* )*/public function show(int $id){// ...}注意 docblock 里参数是用等号赋值而不是冒号:这是注解解析器的语法差异把属性写法直接拷进 docblock 一定解析失败。常见坑点❌ 以为「Swagger 3」是个规范版本号在openapi字段里写swagger: 3.0。✅ 规范版本字段名是openapi值形如3.0.3swagger字段只出现在 2.0 时代的文档里且值为2.0。❌ 生产环境开着L5_SWAGGER_GENERATE_ALWAYStrue每次访问文档页都全量扫描代码。✅ 生产改为false把php artisan l5-swagger:generate放进部署脚本顺带用php artisan config:cache固化配置。❌ 文档路由/api/documentation不加任何保护公网可访问把全部内部接口路径、参数结构都暴露出去。✅ 在config/l5-swagger.php里给对应 documentation 的middleware数组加上auth或自定义鉴权中间件或仅在非生产环境注册该路由。❌ 注解里给路径参数写了required: falseSwagger UI 渲染异常或校验告警。✅ OpenAPI 规范规定路径参数必须为必填写成required: true。❌ Laravel 11 项目没执行php artisan install:api就抱怨文档里没有接口。✅ 先补上routes/api.php确认php artisan route:list能看到接口再重新生成文档。❌ 生成命令跑过了但改了注解后页面没变以为命令没生效。✅ 关掉generate_always时产物是静态文件每次改注解都要重新执行php artisan l5-swagger:generate浏览器可能还会缓存需要强刷。❌ 把storage/api-docs目录提交进版本库或者从没确认过 Web 服务器对该目录有没有写权限。✅ 产物属于构建输出加进.gitignore部署时确认 PHP 进程对storage有写权限否则生成命令会失败。总结环节关键动作常见失败原因概念写的是 OpenAPI用 Swagger UI 展示把规范名与工具名混用字段写错安装composer require darkaonline/l5-swagger版本与 PHP / Laravel 约束不匹配配置发布config/l5-swagger.php、设置两个L5_环境变量生产环境仍开启每次生成编写PHP 8 用属性PHP 7 用 docblock 注解两套语法互相照抄生成php artisan l5-swagger:generate改了注解没重新生成访问默认api/documentation路由未加鉴权保护结论Swagger/OpenAPI 方案的价值在于文档与代码同源、且是一份机器可读的标准文件可以直接喂给前端代码生成器或接口测试工具。代价是你必须在控制器上维护注解注解写错的排查成本不低所以在团队里最好约定一套最小必填字段summary、tags、参数、主要响应状态码而不是每个接口都写到极致。如果你更希望「零注解」可以考虑基于路由扫描与 FormRequest 推断文档的方案例如 Scribe、Scramble 这类工具它们上手快但对文档的精确控制力会弱一些——选哪条路取决于你的接口是内部自用还是对外开放。