🐘 遗留 PHP 项目冲 Level 9?这份渐进式启用路线请收好

发布于
7

PHPStan 1.0 起级别体系扩展到 0–9,其中 level 9 在 level 8 基础上增加了一项关键检查:严格的 mixed 类型比较——所有 mixed 参与的操作都会被视为错误,等价于开启了最高严格度(配置中也可用 level: max 作为别名)。对全新项目,从第一天就锚定 level 8/9 是合理目标;但对动辄数万行、类型注解残缺的遗留代码库,直接 --level=9 往往会瞬间爆出几千条错误,团队信心直接劝退 。

🎯 核心认知:Level 9 不是起点,而是终点

PHPStan 各级的递进关系:

  • Level 0–2:未知类、未定义变量、未知方法

  • Level 3–4:返回值类型检查、死代码检测

  • Level 5–6:方法参数类型校验、缺失类型提示告警

  • Level 7–8:联合类型收窄、nullable 严格检查

  • Level 9mixed 类型视为错误,要求代码完全类型化

💡 在遗留项目里,正确姿势是”从代码库当前能通过的级别起步,逐级抬升”,而不是反过来 。

🛠️ 五步渐进式启用策略

第一步:选一个”能跑通”的起始级别

不要一上来就 level 9。先跑:

vendor/bin/phpstan analyse --level=6 src

选一个团队当前能承受、错误量可控的级别作为起点(多数遗留项目从 5 或 6 起步比较现实)。

第二步:生成基线,冻结存量债务

vendor/bin/phpstan analyse --level=8 --generate-baseline

这条命令会产出 phpstan-baseline.neon,记录当下所有错误及其在每个文件中的出现次数 。在主配置中引入:

includes:
    - phpstan-baseline.neon

parameters:
    level: 8
    paths:
        - src
    excludePaths:
        - src/Legacy
        - database/migrations/*

此时再跑分析,存量错误全部被抑制,PHPStan 只对新代码中的违规说不——基线的本质是一份”技术债务台账”,而不是永久豁免牌 。

第三步:接进 CI,形成”防回流”闸门

把 PHPStan 挂到持续集成流程中。基线存在的前提下,CI 对存量代码是通过的,但任何 PR 引入的新错误都会让构建失败​ 。这一步至关重要:它把静态分析从”一次性大扫除”变成了”棘轮”,只进不退。

第四步:按文件/模块啃基线,而非按错误类型

很多团队的踩坑点:两人同时修不同错误后各自 regenerate baseline,造成 phpstan-baseline.neon 的合并冲突。

正确做法:

  1. 按文件/模块分配:每人认领几个文件,彻底修完

  2. 修完一批就 regeneratevendor/bin/phpstan analyse --level=8 --generate-baseline

  3. 观察基线文件行数下降——这是遗留项目最爽的进度条

配合 reportUnmatchedIgnoredErrors: true,可以清理掉那些已经不匹配任何错误的陈旧忽略规则,避免它们悄悄掩盖后续新引入的问题 。

第五步:基线清空后,抬到下一级

当 level N 的基线条目归零,把配置切到 level N+1,重新生成基线,循环往复。如此一步步逼近 level 9。整个过程可能跨越多个 sprint,但业务功能开发完全不被阻塞 。

⚠️ 三个容易翻车的点

1. 别用 @phpstan-ignore 无脑压错误

PHPStan 2.1.41+ 已支持 reportIgnoresWithoutComments,强制要求每个忽略都附带解释性注释,且不再允许 @phpstan-ignore-line / @phpstan-ignore-next-line 这种无标识的写法 。这倒逼团队想清楚”为什么忽略”,而不是把静态分析变成自欺欺人。

2. 排除真正的”噪音源”

自动生成的迁移文件、API spec 生成的 DTO、框架魔法方法密集的 Model 层,可通过 excludePaths 排除或借助框架专用扩展(如 Laravel 的 Larastan、Symfony 的 phpstan/phpstan-symfony)增强类型推断 。

3. 严格规则包是补充,不是替代

composer require phpstan/phpstan-strict-rules --dev
includes:
    - vendor/phpstan/phpstan-strict-rules/rules.neon

它在级别体系之外追加了”条件语句必须布尔”、”禁止松散比较 ==“等规则 ,属于锦上添花,不能代替 level 9 本身的 mixed 严格检查。

📊 预期收益与节奏

经验数据:对于一个 8 万行左右的遗留代码库,从 level 5 起步、每季度抬升 1–2 级、每 sprint 分配 15–20% 工时啃基线,通常 3–4 个季度可稳定抵达 level 8,向 level 9 收敛则需更长的类型补全周期,尤其是补全函数返回值、属性类型、集合泛型这三个重灾区。

📌 衡量成功的指标不是”今天到了哪一级”,而是”基线文件在不在缩小”。只要基线逐月变矮,方向就是对的。

PHPStan level 9 不是一个开关,而是一种纪律。基线让遗留项目”先止血、再疗伤”,逐级抬升把大目标拆成可交付的小步进,CI 闸门保证债务不新增——三者配合,level 9 才从空中楼阁变成可抵达的彼岸。

常见问题(FAQ)

PHPStan 的级别体系是如何划分的?
PHPStan 的级别体系从 0 到 9,其中: - Level 0–2 专注于未知类、未定义变量和未知方法的检测。 - Level 3–4 引入返回值类型检查和死代码检测。 - Level 5–6 检查方法参数类型并告警缺失类型提示。 - Level 7–8 包括联合类型收窄和 nullable 严格检查。 - Level 9 要求所有代码完全类型化,将 mixed 类型视为错误。
为什么在遗留项目中不能直接启用 Level 9?
直接启用 Level 9 会导致大量错误,因为遗留项目中可能有数万行代码且类型注解残缺。这会打击团队信心,因此推荐从较低级别起步,逐步提升,让团队适应类型检查的过程。
如何为遗留项目生成基线并冻结存量错误?
使用命令 `vendor/bin/phpstan analyse --level=8 --generate-baseline` 生成基线文件 `phpstan-baseline.neon`,它记录当前所有错误。在配置中引入该基线文件并设置 level 为 8,这样 PHPStan 只会检查新代码中的错误,不会对存量代码重复报错。
如何防止团队成员在修复错误时产生冲突?
建议按文件或模块分配任务,每人认领几个文件并彻底修复。修复一批文件后重新生成基线,观察基线文件行数下降。使用 `reportUnmatchedIgnoredErrors: true` 可以清理不再需要的忽略规则,避免冲突。
除了 Level 9,还有哪些规则可以增强类型检查?
PHPStan 的严格规则包(phpstan-strict-rules)可以在级别体系之外添加额外检查,如禁止松散比较(==)和要求条件语句必须为布尔类型。但这不是 Level 9 的替代,而是补充,Level 9 本身通过将 mixed 视为错误来强制完全类型化。
0 讨论
热门最新
总结
0 / 600
嗨,下午好!
所有的成功,都源自一个勇敢的开始
¥10.00
10元抵扣券
已过期