【免费下载链接】swagger-php
A php swagger annotation and parsing library
本文面向从 swagger-php v4 及更早版本升级到 v5 的开发者,系统梳理 v5 这一"清理型"主版本带来的全部破坏性变更:最低 PHP 版本提升、legacy TokenAnalyser 与 --legacy CLI 选项的移除、默认解析方式转向 PHP Attributes、底层 PHP 解析引擎切换为 nikic/php-parser,以及多组废弃 API 的删除与替代方案。读完本文,你将能对照清单逐项检查自己的项目,快速完成升级并规避迁移陷阱,同时理解当前仓库(v6.x)中这些机制如何延续与演化。
一、v5 概览:一次以"清理"为主题的版本升级
根据仓库文档 migrating-to-v5.md,v5 的核心定位是 mostly a cleanup release with updated dependencies(一次以清理与依赖更新为主的发布)。它并不引入新的 OpenAPI 特性或全新的注解体系,而是集中处理以下五类变更:
- 最低要求的 PHP 版本提升为 7.4;
- 移除 legacy
TokenAnalyser以及 CLI 的--legacy选项; - 默认解析策略改为 优先使用 Attributes(属性) 而非 DocBlock 注解;
- PHP 源码解析改用
nikic/php-parser库; - 移除一组已废弃的特性(详见下文"废弃功能移除"章节)。
文档特别强调:对于大多数安装,升级不需要做任何代码改动。这得益于 v5 主要删除的是"可选的老路径",而不是改变默认行为本身——如果你的项目从 v4 开始就使用 Attributes 与现代 Generator API,升级通常是平滑的。
需要说明的是:本文所依据的当前仓库已处于 6.x 开发线(见 composer.json 中 "branch-alias": {"dev-master": "6.x-dev"}),因此文中会结合当前源码展示 v5 引入的机制在后续版本中的实际形态,供读者对照参考。
二、环境要求:最低 PHP 版本提升到 7.4
v5 将最低 PHP 版本从旧版的 7.x 低版本提升到 7.4。这一门槛直接决定了你能否使用 v5 及其之后的版本。
composer require zircote/swagger-php:^5.0
从当前仓库的 composer.json 可以看到,这条"最低版本"的曲线在后续版本中继续上移:仓库现版本要求 "php": ">=8.2"。也就是说,如果你的生产环境 PHP 版本停留在 7.4,只能停留在 v5 时代;若已升级到 8.2+,则可以继续跟进到当前版本线。
升级前建议先核对运行环境的 PHP 版本:
php -v
三、移除 legacy TokenAnalyser 与 --legacy CLI 选项
3.1 背景:v4 时代的"双轨解析"
在 v4 迁移文档 migrating-to-v4.md 中明确写道:"Legacy support is available via the previous TokenAnalyser"。也就是说,v4 为了保证从更早版本平滑过渡,保留了基于 PHP token 扫描的老式解析器 TokenAnalyser,并提供了 --legacy CLI 选项让用户可以显式选择老解析路径。
3.2 v5 的移除动作
v5 正式移除了这条 legacy 路径:
TokenAnalyser类被删除,不再提供基于 token 的老式解析能力;- CLI 的
--legacy选项被删除,命令行不再有"退回老解析器"的开关。
移除之后,所有解析统一走基于反射与 AST 的新路径。在当前的 bin/openapi 与 src/Console/GenerateCommand.php 中已经看不到任何 --legacy 选项,CLI 提供的选项为:--config/-c、--defaults/-D、--output/-o、--format/-f、--exclude/-e、--pattern/-n、--bootstrap/-b、--add-processor/-a、--remove-processor/-r、--version、--mode/-m、--debug/-d(见 GenerateCommand::configure())。
3.3 新解析路径:ReflectionAnalyser
替代 TokenAnalyser 的是 ReflectionAnalyser。从当前 src/Generator.php 的默认构建逻辑可以看到,Generator 默认的分析器是:
$this->analyser = $this->analyser ?: new ReflectionAnalyser([
new AttributeAnnotationFactory($generatorConfig['ignoreOtherAttributes']),
new DocBlockAnnotationFactory(),
]);
即默认由两个"注解工厂"组成:AttributeAnnotationFactory(读取 PHP 8 属性)与 DocBlockAnnotationFactory(读取 DocBlock 注解)。ReflectionAnalyser 的完整实现位于 src/Analysers/ReflectionAnalyser.php,其工作原理是:
- 用
TokenScanner(注意是OpenApi\Utils\TokenScanner)对源文件做一次预扫描,得到每个类/接口/ trait/enum 的结构信息(uses、接口、trait、方法、属性、常量等); - 通过
ClassReflector::tryReflect()反射加载对应类; - 依次对类、方法、属性、常量调用各注解工厂的
build(),收集OA\AbstractAnnotation实例; - 将收集到的注解与上下文(
Context)注册进Analysis。
文档指出 ReflectionAnalyser 由于依赖反射,"requires all related classes to be auto-loadable",这一点与旧 TokenAnalyser 可以在不加载类的情况下工作不同,是迁移后需要注意的一个行为差异。
另外可以留意到,当前仓库在 src/Analysers/TokenScanner.php 仍保留着一个同名类,但它只是指向 \OpenApi\Utils\TokenScanner 的过渡外壳,并带有明确的废弃标记("removed in 7.0")——这是 v5 之后命名空间调整留下的痕迹。
四、默认优先 Attributes:注解方式从"主角"变成"配角"
4.1 默认顺序的转变
v5 起,默认解析策略从"注解优先"切换为"Attributes 优先"。这在 Generator::getAnalyser() 的工厂数组顺序中体现得很直接:AttributeAnnotationFactory 排在 DocBlockAnnotationFactory 之前(src/Generator.php)。文档 using-attributes.md 也开宗明义:"PHP Attributes are the preferred way to annotate your code."
在 v4 及更早,DocBlock 注解是主要书写方式,PHP 8.1 引入的嵌套属性能力(new 表达式可用于初始化器)后才让 Attributes 具备与注解同等的表达能力(详见 migrating-to-v4.md 中 "Annotations as PHP attributes" 一节)。v5 将这一技术趋势固化为默认策略。
4.2 注解与 Attributes 的写法对比
同为声明 Info + License,v4 时代的 DocBlock 写法是:
use OpenApi\Annotations as OA;
/**
* @OA\Info(
* version="1.0.0",
* title="My API",
* @OA\License(name="MIT"),
* )
*/
class OpenApiSpec
{
}
v5 推荐的 Attributes 写法(命名参数 + 可嵌套):
use OpenApi\Attributes as OA;
#[OA\Info(
version: '1.0.0',
title: 'My API',
)]
#[OA\License(name: 'MIT')]
class OpenApiSpec
{
}
4.3 Attributes 的合并机制
Attributes 的一个显著特点是:在没有歧义的前提下,可以全部写在顶层,由解析器自动完成父子合并。文档 using-attributes.md 给出的示例是:
#[OA\Get(path: '/api/users')]
#[OA\Response(response: 200, description: 'OK')]
#[OA\Response(response: 401, description: 'Not allowed')]
public function users() { /* ... */ }
与嵌套写法等价。该合并逻辑实现在 src/Analysers/AttributeAnnotationFactory.php:build() 在收集到同一声明位置的全部属性后,会遍历两两判断父子关系(matchNested() 判定显式父子、Attachable 通过 allowedParents() 决定允许嵌套的父类型),把子属性 merge 进父属性,最后只返回"没有父级的根属性"。需要留意的是,文档明确警告:自动合并只发生在顶层声明位置(例如同一个方法上的多个属性),嵌套内容仍需按规范书写。
4.4 对 DocBlock 注解的态度
"优先 Attributes"不等于"删除注解支持"。在 src/Analysers/DocBlockAnnotationFactory.php 中,DocBlock 注解仍被支持,但已带有明确的废弃标记("removed in 8.0 - use attributes instead"),并且只有在 DocBlockParser::isEnabled()(即安装了 doctrine/annotations 时)才生效。该工厂还在实际解析到第一条 DocBlock 注解时通过 trigger_deprecation() 输出弃用提示(DocBlockAnnotationFactory.php)。这进一步印证了 v5 确立的长期方向:Attributes 是正统,注解是兼容通道。
五、PHP 解析迁移到 nikic/php-parser
5.1 依赖层面的变化
v5 起,swagger-php 的 PHP 源码解析不再依赖自研的 token 扫描逻辑,而是引入业界通用的 nikic/php-parser 库。这一点在当前仓库的 composer.json 中依然可见:
"require": {
"php": ">=8.2",
"nikic/php-parser": "^4.19 || ^5.0",
...
}
5.2 底层实现
当前仓库的 src/Utils/TokenScanner.php 展示了这条解析链路的实际形态:scanFile() 读取文件后,使用 (new ParserFactory())->createForNewestSupportedVersion() 创建与当前 PHP 版本匹配的解析器,将源码解析为 AST(抽象语法树),再遍历 Namespace_、ClassLike(含 Class_、Interface_、Trait_、Enum_)、ClassMethod、Use_ 等节点,收集类结构信息(见 TokenScanner::scanFile())。方法对每个文件名做了结果缓存,重复扫描同一文件直接返回缓存结果。
从源码结构可以推断,相比早期基于 token_get_all() 的字符串级 token 扫描,基于 php-parser 的 AST 解析有以下优势:
- 语法结构信息更完整(namespace、use 别名、继承/实现关系、枚举等都能结构化提取);
- 对新语法(
enum、构造器属性提升、readonly等)的支持更稳定,由 php-parser 的版本演进保证; - 解析错误处理更明确:解析失败会抛出
PhpParser\Error,由scanFile()包装为RuntimeException。
ReflectionAnalyser::fromFile() 正是先调用这个 TokenScanner 获得文件结构信息,再基于反射构建注解上下文(见 src/Analysers/ReflectionAnalyser.php)。
六、移除的废弃功能与替换方案
v5 删除了三类废弃元素,下面是每一项的替换路径与源码佐证。
6.1 移除 ProcessorInterface(空接口/未被使用)
v4 迁移文档提到,处理器(processor)在 v4 时已经从 Analysis 移入 Generator 类(见 migrating-to-v4.md 中 \Openapi\Analysis::processors() 的移除说明)。到 v5,废弃的 ProcessorInterface 被彻底删除。
从当前仓库看,处理器不再是"实现某个接口的类",而是实现了 __invoke() 的可调用对象。流水线 Pipeline 的泛型约束为 PipeInterface|(callable&object)(见 src/Utils/Pipeline.php),即管道元素既可以是实现了 PipeInterface 的类,也可以是任何"可调用的对象"。以典型的 CleanUnmerged 处理器为例(src/Processors/CleanUnmerged.php):
class CleanUnmerged
{
public function __invoke(Analysis $analysis): void
{
// 拆分 merged/unmerged 注解并整理 _unmerged 引用
}
}
Pipeline::process() 按顺序调用每个元素:$payload = $pipe($payload) ?? $payload;(见 Pipeline::process()),并自动为实现了 LoggerAwareInterface 的元素注入 logger。这就是 v5 之后"自定义处理器"的标准形态。
6.2 移除 Context::clone() 与 Context::detect()
这两个静态/实例工具方法在 v5 中被删除。从当前 src/Context.php 的实现可以反推其替代机制:Context 现在通过 构造函数传入属性数组 + parent 原型链 来组织上下文树。
Context::__construct(array $properties, ?Context $parent)将属性直接落到实例上(Context.php);__get()在实例上没有找到属性时,沿parent链向上查找(Context.php)——这就是原先detect()所承担的"从上下文链推断环境信息"职责的替代方案;- 新增的
is()/not()判断属性是否直接定义在当前层,with()向上查找包含指定属性的上下文,root()返回根上下文(Context.php); ensureRoot()负责把孤立上下文挂接到给定的根上下文上,并回填logger、version(Context.php)。
也就是说,迁移后如果你的自定义代码曾调用 Context::detect() 来探测(例如)当前文件名、命名空间或所在类,应改为直接访问 $context->filename、$context->namespace、$context->class 等动态属性(它们会自动沿 parent 链解析);若曾用 clone() 派生新上下文,则改为 new Context([...], $parentContext) 的方式显式构造。Context 同时实现了 Stringable 与 __serialize()/__unserialize(),便于调试输出与序列化。
6.3 移除 \Openapi\Generator::getProcessors() 与 \Openapi\Generator::setProcessors()
这是 v5 废弃移除中最需要动手改代码的一处。原文档给出的替换方案明确:
Use
getProcessorPipeline()andsetProcessorPipeline(new Pipeline(...))methods instead
当前 src/Generator.php 中的对应实现为:
public function getProcessorPipeline(): Pipeline { /* ... */ } // L233-L284
public function setProcessorPipeline(?Pipeline $processor): Generator { /* ... */ } // L289-L304
public function withProcessorPipeline(callable $with): Generator { /* ... */ } // L311-L316
三个方法的职责:
| 方法 | 职责 |
|---|---|
getProcessorPipeline() | 返回(必要时惰性创建)默认处理器流水线,并把 Generator 的配置自动应用到每个处理器上 |
setProcessorPipeline(new Pipeline(...)) | 用自定义 Pipeline 整体替换默认流水线,并为其元素注入 Generator |
withProcessorPipeline(callable $with) | 以链式调用方式就地修改当前流水线(如添加/移除处理器),不替换整体 |
getProcessorPipeline() 内部会构建一份默认流水线,包含 21 个内置处理器(Generator.php):
DocBlockDescriptions → MergeIntoOpenApi → MergeIntoComponents → ExpandClasses
→ ExpandInterfaces → ExpandTraits → ExpandEnums → AugmentSchemas
→ AugmentRequestBody → AugmentProperties → AugmentDiscriminators → BuildPaths
→ AugmentParameters → AugmentRefs → AugmentItems → MergeJsonContent
→ MergeXmlContent → AugmentMediaType → OperationId → CleanUnmerged
→ PathFilter → CleanUnusedComponents → AugmentTags
流水线构建后,getProcessorPipeline() 还会通过 walk() 对每个处理器做两件事:根据配置数组(以处理器短名小写开头为键,如 operationId.hash=false)调用对应的 setter,以及为实现了 GeneratorAwareInterface 的处理器注入 Generator(Generator.php)。
迁移示例:自定义处理器替换 setProcessors()
v4 时代的写法(已废弃):
$generator->setProcessors([
new MyProcessor(),
new \OpenApi\Processors\CleanUnmerged(),
// ...
]);
v5 及以后的写法:
use OpenApi\Generator;
use OpenApi\Utils\Pipeline;
$generator = new Generator();
$generator->setProcessorPipeline(new Pipeline([
new MyProcessor(),
new \OpenApi\Processors\CleanUnmerged(),
// ...
]));
如果只想在默认流水线基础上做局部调整,使用 withProcessorPipeline() 更符合"最小改动"的迁移原则:
$generator->withProcessorPipeline(function (Pipeline $pipeline): void {
$pipeline->add(new MyProcessor()); // 末尾追加
$pipeline->remove(\OpenApi\Processors\CleanUnusedComponents::class); // 移除内置处理器
});
Pipeline 本身继承自 TypedList,提供了 add()、remove()、clear()、count() 等操作(见 src/Utils/TypedList.php)。测试代码中也大量使用这一新 API,例如 tests/Analysers/ReflectionAnalyserTest.php 中的 $this->processorPipeline(strip: [CleanUnusedComponents::class])->process($analysis);,以及 tests/OpenApiTestCase.php 对分析器/解析器的统一装配,可作为迁移后写测试的参考。
七、升级检查清单
对照 v5 文档列出的全部变更,迁移时可逐项核对:
- PHP 版本:确认运行环境 ≥ 7.4;若计划继续跟进 v6+,需 ≥ 8.2(见 composer.json 的
require.php)。 - CLI 调用:删除脚本或文档中任何
--legacy参数;当前 CLI 用法以 src/Console/GenerateCommand.php 的选项为准。 - 解析器依赖:确认 composer 依赖树中包含
nikic/php-parser(^4.19 || ^5.0),不必再依赖旧的 token 解析逻辑。 - 书写方式:新代码优先使用
OpenApi\Attributes命名空间的 PHP 8 Attributes;旧 DocBlock 注解仍可工作但会触发弃用提示(DocBlockAnnotationFactory.php),建议逐步迁移。 - 自定义处理器:检查是否引用了
ProcessorInterface,改为实现__invoke(Analysis $analysis)的可调用对象,并通过Pipeline挂载。 - Context 使用:搜索
Context::clone()、Context::detect()调用,改为new Context([...], $parent)与属性直读/is()/with()等新 API(见 src/Context.php)。 - Generator API:搜索
getProcessors()、setProcessors()调用,替换为getProcessorPipeline()、setProcessorPipeline(new Pipeline(...))或withProcessorPipeline()(见 src/Generator.php)。 - 回归验证:迁移后用
./vendor/bin/openapi重新生成规范文档,与迁移前的输出做 diff;可参考仓库内置的DocsAccuracyTest(见 composer.json 的docs:check脚本)思路建立文档正确性校验。
八、与后续版本(v6)的关系
v5 确立的"清理"方向在 v6 中得到延续。对照 migrating-to-v6.md 可以看到,v6 又一次提高了最低 PHP 版本(8.2)、继续移除废弃元素(如 Generator::scan() 静态方法、Utils helper),并把 CLI 的处理器相关选项更名为 --add-processor/-a、新增 --remove-processor/-r——这正是 v5 将处理器管理收敛到 Pipeline 之后的自然延伸:命令行可以动态地向流水线追加或移除处理器(对应 src/Console/GenerateCommand.php 的 withGenerator() 回调逻辑)。
因此,从迁移策略上讲:如果你的项目已经按本文清单完成 v5 迁移(特别是全面使用 Attributes 与 getProcessorPipeline()/setProcessorPipeline()),那么后续 v6 的升级成本会大幅降低——这两条正是 v5 为整个 5.x/6.x 时代打下的地基。
参考资源
- 迁移文档:docs/guide/migrating-to-v5.md、docs/guide/migrating-to-v4.md、docs/guide/migrating-to-v6.md
- Attributes 使用指南:docs/guide/using-attributes.md
- 核心实现:src/Generator.php、src/Utils/Pipeline.php、src/Analysers/ReflectionAnalyser.php、src/Context.php
- 解析器实现:src/Utils/TokenScanner.php
- CLI 入口:bin/openapi、src/Console/GenerateCommand.php
- 测试参考:tests/OpenApiTestCase.php、tests/Analysers/ReflectionAnalyserTest.php
【免费下载链接】swagger-php
A php swagger annotation and parsing library
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



