PHPStan 错误标识符文档体系全解析:从 Rule 源码到自动生成文档的工程实践
2026/9/23 11:16:29 网站建设 项目流程

PHPStan 错误标识符文档体系全解析:从 Rule 源码到自动生成文档的工程实践

【免费下载链接】phpstanPHP Static Analysis Tool - discover bugs in your code without running it!项目地址: https://gitcode.com/gh_mirrors/ph/phpstan

PHPStan 在报告每个错误时都会附带一个稳定且可机器识别的错误标识符(Error Identifier,如property.notFounddeadCode.unreachable),而 website/errors/CLAUDE.md 正是这套标识符文档体系的核心规范文件。它规定了 phpstan.org 上每个错误详情页的生成方式、Markdown 文件格式、写作规则与标识符前缀语义。读完本文,你将完整掌握 PHPStan 错误标识符文档的结构化格式、自动化生成流水线、全部标识符前缀的含义,以及如何通过源码追溯一个标识符从 Rule 规则到文档的完整生命周期。

一、这套文档体系是什么

website/errors/目录存放的是 PHPStan 错误标识符的 Markdown 文档,每个文件对应一个标识符,例如 website/errors/deadCode.unreachable.md、website/errors/property.notFound.md。每个文件负责回答三个问题:

  1. 这个错误具体是什么意思;
  2. 什么代码会触发它(附最小复现代码);
  3. 有哪些修复方式。

这不仅是给开发者查阅的静态文档,也是一套被 AI(Claude)自动化维护的"活"文档:每当 PHPStan 新增或调整标识符,工作流会自动为尚无文档的标识符补齐说明,保证 phpstan.org 的错误标识符页面 永远不缺页。

二、文档如何被自动生成

根据 website/errors/CLAUDE.md,这些文件由一个 GitHub Actions 工作流(.github/workflows/generate-error-docs.md)驱动,使用 Claude 按以下步骤生成:

  1. 读取 website/src/errorsIdentifiers.json——该文件把每个标识符映射到其对应的 Rule 类与源码位置(该文件在仓库中超过 1.8 万行,例如argument.bitmaskNotAllowed同时映射到ClassAttributesRuleInstantiationRuleCallMethodsRule等十余个 Rule 类);
  2. 挑选出"尚未被文档覆盖"的标识符(即在website/errors/下没有对应.md文件的标识符);
  3. 克隆相关的 PHPStan 仓库(phpstan-srcphpstan-strict-rulesphpstan-doctrine等,覆盖核心规则与生态扩展规则);
  4. 逐个研读 Rule 源码与测试夹具(test fixtures),理解错误触发条件;
  5. 为每个标识符生成一份 Markdown 文档。

这套流程的触发端在仓库中同样可见:.github/workflows/claude-update-error-docs-identifiers-change.yml 监听2.3.x分支上website/src/errorsIdentifiers.json的变化,一旦标识符映射表更新,就通过gh workflow run触发下游的文档更新任务。

三、文档文件的标准格式

每个错误文档文件遵循完全一致的模板结构:

--- title: "<identifier>" shortDescription: "One sentence describing when this error is reported." ignorable: true --- ## Code example ```php <?php declare(strict_types = 1); // Minimal PHP code that triggers this error

Why is it reported?

Explanation from PHP language perspective.

How to fix it

Ways to fix the error.

### Frontmatter 字段 | 字段 | 含义与取值规则 | |------|---------------| | `title` | 错误标识符本身,例如 `"property.notFound"` | | `shortDescription` | 一句话(以句号结尾)从用户视角描述什么代码模式会导致 PHPStan 报告此错误。示例:`"Accessing a private property from outside the declaring class."`、`"Loose comparison using == will always evaluate to true."`、`"Pure function uses print, which produces output as a side effect."` | | `ignorable` | 绝大多数标识符为 `true`;对使用了 `->nonIgnorable()` 的规则链,或以 `phpstan.`、`phpstanPlayground.` 开头的标识符,必须设为 `false` | `ignorable: false` 的判断依据来自规则构造链中的 `->nonIgnorable()` 调用,这类错误不允许用户通过 `@phpstan-ignore` 或配置文件忽略,因此详情页也不会提供"忽略此错误"的引导。 ### Code example 部分 - 必须是能真实触发该标识符的合法 PHP 代码; - 必须以 `<?php declare(strict_types = 1);` 开头; - 使用 `php` 语言标签; - 保持最小化——移除无关类、简化命名; - 优先直接采用测试夹具(test fixtures)中的真实代码,保证示例与 Rule 测试行为一致。 ### "Why is it reported?" 部分 - 从 **PHP 语言语义** 角度解释,而非讲解 PHPStan 内部实现; - 核心判据:PHPStan 指向的是会导致崩溃、根本不会执行、或与开发者意图不符的代码; - 有多个原因时逐条列出; - 若规则的 `->tip()` 链接到 phpstan.org 上的博客文章,需注明 `Learn more: Blog post title`。 ### "How to fix it" 部分 修复建议遵循固定的优先级顺序: 1. 修复真正的 bug; 2. 使用原生 PHP 类型声明收窄类型; 3. 使用 PHPDoc 类型收窄(`@param`、`@return`、属性上的 `@var`); 4. 在函数体内使用类型收窄(type narrowing)技巧; 5. 若该规则可配置,则配置 PHPStan。 代码改动一律使用 `diff-php` 语法展示: ````markdown ```diff-php - $value = $this->getValue(); + $value = (string) $this->getValue();
此外,规范还明确要求: - 当错误涉及仅在较新 PHP 版本可用的语言特性时,必须同时给出在旧版本可用的 PHPDoc 替代写法——例如原生返回类型 `never`(PHP 8.1+)可用 `@return never` 替代,原生联合类型(PHP 8.0+)可写成 PHPDoc 联合类型,原生交叉类型(PHP 8.1+)可写成 PHPDoc 交叉类型,`true`/`false`/`null` 这类独立类型(PHP 8.2+)也可写在 PHPDoc 中; - 每次提到配置参数,都必须链接到正确的配置文档锚点(依据 [website/src/config-reference.md](https://link.gitcode.com/i/46c48ca532ae9537ba8d02211eb0c80b) 判断):拥有独立 `###` 标题的参数(如 `phpVersion`)链接到 `/config-reference#phpversion`;只出现在 "Related config keys" 中的参数则链接到对应用户指南页面(如 `reportUnmatchedIgnoredErrors` 链接到忽略错误章节,`scanFiles` 链接到符号发现章节)。 ### Do NOT 清单 编写文档时严格禁止: - 建议使用 `assert()` 做类型收窄; - 建议抛出异常来收窄类型; - 建议使用行内 `@var` PHPDoc 标签; - 建议直接忽略错误(详情页本身已覆盖忽略方式); - 使用 emoji 或第一人称。 ## 四、标识符前缀参考表 标识符的命名不是随意的:一部分前缀语义并不直观,因为它们源自 PHPStan 内部枚举 `ClassNameUsageLocation`。规范文件中给出了完整对照: | Prefix | PHP Feature | |--------|-------------| | `assert` | `@phpstan-assert` PHPDoc 标签(注意:**不是** `assert()` 函数) | | `attribute` | PHP 8.0+ 属性 `#[AttributeName]` | | `catch` | `catch (ExceptionClass $e)` 块 | | `classConstant` | `ClassName::CONSTANT` 访问 | | `instanceof` | `$x instanceof ClassName` 表达式 | | `methodTag` | `@method` PHPDoc 标签 | | `mixin` | `@mixin` PHPDoc 标签 | | `new` | `new ClassName()` 实例化 | | `parameter` | 函数/方法参数上的原生类型声明 | | `property` | 类属性上的原生类型声明(如 `private Foo $bar`) | | `propertyTag` | `@property` PHPDoc 标签 | | `requireExtends` | `@phpstan-require-extends` PHPDoc 标签 | | `requireImplements` | `@phpstan-require-implements` PHPDoc 标签 | | `return` | 原生返回类型声明 | | `sealed` | `@phpstan-sealed` PHPDoc 标签 | | `selfOut` | `@phpstan-self-out` PHPDoc 标签 | | `staticMethod` | `ClassName::method()` 静态方法调用 | | `staticProperty` | `ClassName::$property` 静态属性访问 | | `traitUse` | 类体中的 `use TraitName` | | `typeAlias` | PHPStan 类型别名引用 | | `varTag` | `@var` PHPDoc 标签 | ### 特殊格式的标识符前缀 除上述前缀外,还有一类由前缀 + 通配符构成的组合模式: | Prefix pattern | PHP Feature | |----------------|-------------| | `class.extends*` | `class Foo extends ParentClass` | | `class.implements*` | `class Foo implements Interface` | | `enum.implements*` | `enum Foo implements Interface` | | `interface.extends*` | `interface Foo extends OtherInterface` | | `generics.*Bound` | `@template T of BoundClass` 约束边界 | | `generics.*Default` | `@template T = DefaultClass` 默认值 | 这类模式解释了仓库中大量文档文件名的由来,例如 [website/errors/class.extendsDeprecatedClass.md](https://link.gitcode.com/i/a7ce37388f43851adbde1711ace89d5c)、[website/errors/generics.notSubtype.md](https://link.gitcode.com/i/7256fdbea2c22a724fa5e1aa437dd706) 等。 ## 五、语气与风格要求 - 简洁、技术精确、无废话; - 与 phpstan.org 现有文档风格保持一致; - 直接、务实; - 对扩展专属标识符(phpstan-doctrine、phpstan-symfony 等),必须注明提供该规则的扩展包名称——例如 [website/errors/doctrine.dql.md](https://link.gitcode.com/i/8e4705ec2618d75adc20f5edd705dde0) 这类文档会明确指向 Doctrine 扩展。 ## 六、完整示例:deadCode.unreachable 规范文件给出了 `deadCode.unreachable` 的完整参考文档(即 [website/errors/deadCode.unreachable.md](https://link.gitcode.com/i/97fa1ae964f0d785c7347ab87562e4a1) 的真实内容): ```markdown --- title: "deadCode.unreachable" shortDescription: "Code after a return or throw statement can never be executed." ignorable: true --- ## Code example ```php <?php declare(strict_types = 1); function doFoo(): int { return 1; echo 'unreachable'; } ``` ## Why is it reported? The statement after `return` can never be executed. The `return` statement unconditionally transfers control out of the function, making any code following it in the same block dead code. This usually indicates a logic error or leftover code from refactoring. The same applies to other control flow statements that always terminate, such as `throw`, `exit`, `continue`, or `break`. ## How to fix it Remove the unreachable code: ```diff-php function doFoo(): int { return 1; - echo 'unreachable'; } ``` If the code should execute, restructure the logic so it runs before the return: ```diff-php function doFoo(): int { + echo 'this should run'; return 1; - echo 'unreachable'; } ``` ``` 这个示例完整体现了规范的全部要点:最小化触发代码、从 PHP 语言语义(而非 PHPStan 内部机制)解释原因、给出多种修复路径、修复展示使用 `diff-php` 语法、frontmatter 字段齐全。 ## 七、从源码追溯标识符的诞生 标识符文档的质量,最终取决于标识符数据的准确性。仓库中的 `identifier-extractor/` 目录就是负责从 PHPStan 源码中提取标识符与 Rule 类映射关系的独立工具,它直接产出 [website/src/errorsIdentifiers.json](https://link.gitcode.com/i/39e1db4d78b9b0c5cdc7821ded8aad06) 这份驱动文档生成的清单。 其核心机制是两组 Collector(基于 PHPStan 自身的分析器): - [identifier-extractor/src/ErrorWithIdentifierCollector.php](https://link.gitcode.com/i/2a5f396bfbfec0c915e1b1d52b1ae2f2):遍历所有 `MethodCall` 节点,匹配名为 `withIdentifier` 的方法调用,通过 `$scope->getType($args[0]->value)` 解析出作为常量字符串传入的标识符值,并记录调用该方法的 Rule 类名、文件与行号; - [identifier-extractor/src/RuleErrorBuilderCollector.php](https://link.gitcode.com/i/b215614b5cfa3000223a0fca7ca645c9):对称地匹配 `RuleErrorBuilder` 上的 `identifier()` 方法调用,同样解析标识符字符串并记录归属类。 数据输出由 [identifier-extractor/src/ErrorFormatter.php](https://link.gitcode.com/i/9a4fc5d1d56ce98eb9f2688e0a29a969) 完成:它作为 PHPStan 的自定义 ErrorFormatter,将收集到的 `{identifiers, class, file, line}` 组装为 JSON(顶层还包含 `repo` 与 `branch` 环境变量),从而把"哪个 Rule 在哪个文件哪一行注册了哪个标识符"结构化落盘。由此可以看出:**errorsIdentifiers.json 不是手工维护的,而是从 phpstan-src 及各扩展仓库源码中静态分析提取的产物**,这保证了标识符文档与真实规则行为始终一一对应。 ## 八、这套体系对开发者的实用价值 - **使用 PHPStan 的开发者**:理解了标识符前缀表,就能从错误消息中的 `identifier` 一眼判断错误来自原生类型声明、PHPDoc 标签还是 PHP 语言特性(如 attribute、enum、mixin),从而更精准地定位问题; - **扩展(Extension)作者**:通过 `->identifier('myExtension.someError')` 注册自定义标识符后,可以参照 [website/errors/CLAUDE.md](https://link.gitcode.com/i/521b8ff0be4ad5672d8c186363587ad5) 的格式为自己的规则补齐文档;同时也能理解 `ignorable` 字段与 `->nonIgnorable()` 的关系,决定哪些错误允许用户忽略; - **文档贡献者**:只要遵循 frontmatter + 三段式结构、`diff-php` 修复示例、前缀表语义和 Do NOT 清单,就能与官方自动化工作流产出的文档保持完全一致的风格,保证整套错误文档体系的长期可维护性。 从 Rule 源码中的一行 `->identifier(...)`,到提取器静态分析落盘 JSON,再到 Claude 按规范生成 Markdown 详情页——这套以 [website/errors/CLAUDE.md](https://link.gitcode.com/i/521b8ff0be4ad5672d8c186363587ad5) 为中枢的流水线,构成了 PHPStan 文档生态中一个完整、可复用的自动化工程范例。

【免费下载链接】phpstanPHP Static Analysis Tool - discover bugs in your code without running it!项目地址: https://gitcode.com/gh_mirrors/ph/phpstan

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询