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 9:
mixed类型视为错误,要求代码完全类型化
💡 在遗留项目里,正确姿势是”从代码库当前能通过的级别起步,逐级抬升”,而不是反过来 。
🛠️ 五步渐进式启用策略
第一步:选一个”能跑通”的起始级别
不要一上来就 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 的合并冲突。
正确做法:
-
按文件/模块分配:每人认领几个文件,彻底修完
-
修完一批就 regenerate:
vendor/bin/phpstan analyse --level=8 --generate-baseline -
观察基线文件行数下降——这是遗留项目最爽的进度条
配合 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 --devincludes:
- 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 才从空中楼阁变成可抵达的彼岸。

