- 开发工具
- 代码质量
- 质量保障
【免费下载链接】psalm
A PHP static analysis tool for finding errors and security vulnerabilities in PHP applications
Psalm 是 PHP 静态分析工具,其MissingThrowsDocblock问题用于在启用checkForThrowsDocblock配置后,强制要求函数与方法在抛出或未处理异常时提供@throws注解,从而让异常契约成为代码库中可被静态检查的显式声明。本文围绕该问题类型的触发条件、配置方式、底层检测原理、忽略规则与自动修复能力展开,帮助你将其接入现有工程并理解其工作细节。
什么是 MissingThrowsDocblock
官方文档对它的定义非常简洁:当checkForThrowsDocblock配置项被启用时,Psalm 会在「函数抛出了异常(或未能处理某个异常),却没有@throws注解」时发出该问题。
最典型的一个触发示例(来自 MissingThrowsDocblock.md):
<?php function foo(int $x, int $y) : int { if ($y === 0) { throw new \InvalidArgumentException('Cannot divide by zero'); } return intdiv($x, $y); }函数foo在$y === 0时主动抛出InvalidArgumentException,但由于函数体内部没有try/catch处理它,这个异常会沿调用链向上传播。在开启checkForThrowsDocblock的前提下,Psalm 会在此处报告MissingThrowsDocblock,并给出类似提示:
InvalidArgumentException is thrown but not caught - please either catch or add a @throws annotation也就是说,Psalm 要求开发者二者择一:要么在函数内部捕获异常,要么在 docblock 中明确声明它可能抛出什么。
开启与配置
MissingThrowsDocblock默认不启用,必须先开启配置项才会检测。对应配置在 configuration.md 中描述:
<psalm checkForThrowsDocblock="[bool]" >当值为true时,Psalm 会检查开发者是否为函数或方法抛出的每一个异常提供了@throwsdocblock,默认值为false。
从源码看,配置解析在 Config.php 中完成:第 354 行定义了public bool $check_for_throws_docblock = false;,第 985 行将 XML 属性名checkForThrowsDocblock映射到该属性。因此以下两种写法等价:
<psalm checkForThrowsDocblock="true"><psalm checkForThrowsDocblock="false">开启后,上面的foo函数会立即收到MissingThrowsDocblock报告。修复方式是为其补充@throws注解:
<?php /** * @throws \InvalidArgumentException */ function foo(int $x, int $y) : int { if ($y === 0) { throw new \InvalidArgumentException('Cannot divide by zero'); } return intdiv($x, $y); }检测规则与底层原理
MissingThrowsDocblock的判定逻辑位于 FunctionLikeAnalyzer.php,核心步骤如下:
- 调用
$statements_analyzer->getUncaughtThrows($context)获取函数体内所有「可能被抛出且未被捕获」的异常集合,返回格式为exception 名 => CodeLocation 列表(见 StatementsAnalyzer.php)。 - 将该集合与函数存储(
$storage->throws)中已经通过@throws声明的异常逐一比对。 - 比对时不仅要求类名完全相同,还支持继承关系匹配:如果实际抛出的异常是已声明异常的子类,或实现了已声明的接口,也视为「已覆盖」。对应代码为 FunctionLikeAnalyzer.php:
if ($expected_exception === $possibly_thrown_exception || ( $codebase->classOrInterfaceExists($possibly_thrown_exception, null, $context) && ( $codebase->interfaceExtends($possibly_thrown_exception, $expected_exception) || $codebase->classExtendsOrImplements($possibly_thrown_exception, $expected_exception) ) ) ) { $is_expected = true; break; }- 不在期望集合中的异常,会通过
IssueBuffer::maybeAdd上报为MissingThrowsDocblock。
该问题类型定义在 MissingThrowsDocblock.php,继承自ClassIssue,SHORTCODE = 169,属于类级别的静态分析问题。
子类覆盖规则举例
基于上述继承匹配逻辑,下面这种写法是合法的:函数声明抛Exception,实际抛出其子类RuntimeException,不会触发MissingThrowsDocblock:
<?php /** * @throws Exception */ function foo(): void { if (rand(0, 1)) { throw new RuntimeException('boom'); } }反过来,如果@throws声明的是子类而实际抛出父类,则无法匹配,会照常报错。
如何忽略特定异常:<ignoreExceptions>
并非所有异常都值得强制文档化。Psalm 提供了<ignoreExceptions>配置,用于豁免特定异常(或其全部子类)的MissingThrowsDocblock(以及checkForThrowsInGlobalScope)报告,见 configuration.md:
<ignoreExceptions> <class name="fully\qualified\path\Exc" onlyGlobalScope="true" /> <classAndDescendants name="fully\qualified\path\OtherExc" /> </ignoreExceptions>各标签语义:
<class>:只忽略指定类本身。若设置onlyGlobalScope="true",则仅对全局作用域的checkForThrowsInGlobalScope生效,函数与方法内的MissingThrowsDocblock检查不受影响。<classAndDescendants>:忽略指定类及其所有子类。
在实现上,getUncaughtThrows会读取配置中的ignored_exceptions、ignored_exceptions_and_descendants,并区分全局作用域与非全局作用域两组集合(见 StatementsAnalyzer.php)。被豁免的异常会直接跳过,不会进入后续的@throws比对流程。
如何单点压制:@psalm-suppress
对于个别确实需要容忍的位置,不必全局关闭配置,可以使用标准的问题压制注解。测试 ThrowsAnnotationTest.php 中出现了如下写法:
/** @psalm-suppress MissingThrowsDocblock */将其置于函数或方法上方即可跳过该位置的检查,其余位置的强制文档化依然生效。
自动修复:让 Psalter 补全@throws
MissingThrowsDocblock不只是报告问题,Psalm 还支持通过代码修改工具自动补全缺失的@throws注解。相关逻辑位于 FunctionLikeAnalyzer.php:
if ($codebase->alter_code && isset($project_analyzer->getIssuesToFix()['MissingThrowsDocblock']) && !$this->function instanceof VirtualNode ) { $manipulator = FunctionDocblockManipulator::getForFunction( $project_analyzer, $this->source->getFilePath(), $this->function, ); $manipulator->addThrowsDocblock($missingThrowsDocblockErrors); }当以代码修改模式运行(如psalter或--alter)且MissingThrowsDocblock被列入待修复问题清单时,Psalm 会收集所有缺失的异常类型,通过FunctionDocblockManipulator::addThrowsDocblock自动将其写入函数的 docblock。这意味着开启该检查后,你可以放心地一次性修复存量代码:Psalm 负责把缺失的@throws精确补到对应函数上,而不是全部靠手工处理。
测试用例与行为验证
仓库的 ThrowsAnnotationTest.php 为该问题提供了大量回归测试,可帮助理解边界行为,例如:
testUndocumentedThrow:函数抛出多个未声明异常(如RangeException、InvalidArgumentException),预期产生MissingThrowsDocblock(L172-L200)。testDocumentedThrow:同一函数在补全所有@throws后不再报错(L202 起)。testUndocumentedThrowOfGenericClass:即使@throws中使用了泛型语法(如MyException<int>),实际抛出的异常不在声明范围内时仍会触发(L147-L170)。
这些测试通过Config::getInstance()->check_for_throws_docblock = true;开启检查后调用analyzeFile验证,确认了「声明必须与实际抛出匹配」的核心行为。
小结
MissingThrowsDocblock是 Psalm 将异常契约纳入静态检查的开关式能力,适合对库代码、接口层或公共服务要求严格异常文档化的团队。使用它只需三步:在psalm.xml中设置checkForThrowsDocblock="true",用@throws补齐函数与方法的异常声明,必要时借助<ignoreExceptions>与@psalm-suppress处理豁免场景,最后可用psalter自动修复存量代码。它解决的问题本质上是「异常是接口的一部分,应当被显式声明」,从而让调用者从 docblock 中即可获知函数可能抛出的风险。
- 开发工具
- 代码质量
- 质量保障
【免费下载链接】psalm
A PHP static analysis tool for finding errors and security vulnerabilities in PHP applications
相关推荐
Psalm 的 MissingOverrideAttribute 检查:用 `[\Override]` 强制标注重写方法
Psalm 的 MissingOverrideAttribute 检查:用 \Override 强制标注重写方法 导读 :本文围绕 Psalm 的 Missin
开发工具代码质量质量保障Psalm InvalidThrow 完全指南:从异常类型检查到实战排查
Psalm InvalidThrow 完全指南:从异常类型检查到实战排查 摘要导读 本文围绕 Psalm 静态分析器中的 InvalidThrow 问题类型展开
开发工具代码质量质量保障一段10秒视频克隆你的脸和声音:开源数字人 Duix.Avatar 本地部署完整教程
一段10秒视频克隆你的脸和声音:开源数字人 Duix.Avatar 本地部署完整教程 如果你做过数字人视频,多半有过这样的顾虑:要克隆自己的形象和声音,就得把视
人工智能AI 应用数字人媒体生成桌面应用
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考