swagger-php v5 迁移指南:告别 TokenAnalyser,全面拥抱 PHP Attributes 与 ReflectionAnalyser

  • 后端
  • API设计
  • 文档
  • 代码生成

【免费下载链接】swagger-php

A php swagger annotation and parsing library

项目地址: https://gitcode.com/gh_mirrors/sw/swagger-php
点击查看 免费下载

本文面向从 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,其工作原理是:

  1. 用 TokenScanner(注意是 OpenApi\Utils\TokenScanner)对源文件做一次预扫描,得到每个类/接口/ trait/enum 的结构信息(uses、接口、trait、方法、属性、常量等);
  2. 通过 ClassReflector::tryReflect() 反射加载对应类;
  3. 依次对类、方法、属性、常量调用各注解工厂的 build(),收集 OA\AbstractAnnotation 实例;
  4. 将收集到的注解与上下文(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() and setProcessorPipeline(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 文档列出的全部变更,迁移时可逐项核对:

  1. PHP 版本:确认运行环境 ≥ 7.4;若计划继续跟进 v6+,需 ≥ 8.2(见 composer.json 的 require.php)。
  2. CLI 调用:删除脚本或文档中任何 --legacy 参数;当前 CLI 用法以 src/Console/GenerateCommand.php 的选项为准。
  3. 解析器依赖:确认 composer 依赖树中包含 nikic/php-parser(^4.19 || ^5.0),不必再依赖旧的 token 解析逻辑。
  4. 书写方式:新代码优先使用 OpenApi\Attributes 命名空间的 PHP 8 Attributes;旧 DocBlock 注解仍可工作但会触发弃用提示(DocBlockAnnotationFactory.php),建议逐步迁移。
  5. 自定义处理器:检查是否引用了 ProcessorInterface,改为实现 __invoke(Analysis $analysis) 的可调用对象,并通过 Pipeline 挂载。
  6. Context 使用:搜索 Context::clone()、Context::detect() 调用,改为 new Context([...], $parent) 与属性直读/is()/with() 等新 API(见 src/Context.php)。
  7. Generator API:搜索 getProcessors()、setProcessors() 调用,替换为 getProcessorPipeline()、setProcessorPipeline(new Pipeline(...)) 或 withProcessorPipeline()(见 src/Generator.php)。
  8. 回归验证:迁移后用 ./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 时代打下的地基。

参考资源

  • 后端
  • API设计
  • 文档
  • 代码生成

【免费下载链接】swagger-php

A php swagger annotation and parsing library

项目地址: https://gitcode.com/gh_mirrors/sw/swagger-php
点击查看 免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值