PHP静态分析工具进阶:配置PHPStan/Psalm的严格模式与自定义规则开发
各位同学,大家好!今天我们来深入探讨PHP静态分析工具PHPStan和Psalm,重点讲解如何配置它们的严格模式以及如何开发自定义规则,以提升代码质量和可维护性。
静态分析的价值
在深入细节之前,先回顾一下静态分析的价值。与动态分析(例如单元测试)不同,静态分析不需要实际运行代码,而是通过分析源代码来发现潜在的错误和代码质量问题。 这意味着可以在开发早期发现问题,避免在运行时出现难以调试的错误。
静态分析可以检测到的常见问题包括:
- 类型错误: 例如,将字符串传递给需要整数的函数。
- 未定义的变量: 访问未初始化的变量。
- 死代码: 永远不会被执行的代码。
- 潜在的空指针异常: 在可能为null的值上调用方法。
- 安全漏洞: 例如,SQL注入或跨站脚本攻击 (XSS)。
- 代码风格问题: 例如,不符合PSR标准的命名或缩进。
PHPStan与Psalm:选择与比较
PHPStan和Psalm是PHP领域最流行的静态分析工具。 它们都提供了强大的类型检查和代码分析功能,但也有一些关键的区别:
| 特性 | PHPStan | Psalm |
|---|---|---|
| 严格程度 | 可配置,从0(最宽松)到9(最严格) | 可配置,通过level和各种配置文件选项 |
| 类型推断 | 较好,能够推断复杂的类型信息 | 非常优秀,尤其擅长于理解复杂类型和泛型 |
| 扩展性 | 通过扩展可以添加自定义规则 | 通过插件可以添加自定义规则和类型定义 |
| 性能 | 通常更快,尤其是在大型项目中 | 在大型项目中可能较慢,但最新的版本做了很多优化 |
| 配置复杂度 | 相对简单,易于上手 | 相对复杂,需要花费更多时间来配置 |
| IDE集成 | 广泛支持,包括PHPStorm、VS Code等 | 广泛支持,包括PHPStorm、VS Code等 |
| 泛型支持 | 相对有限 | 优秀,对泛型支持非常强大 |
| 社区活跃度 | 非常活跃 | 非常活跃 |
| 错误信息可读性 | 良好,能够提供清晰的错误信息和建议 | 良好,但有时可能不如PHPStan清晰 |
选择哪个工具取决于你的项目需求和偏好。 如果你希望快速上手并获得良好的性能,PHPStan可能是一个更好的选择。 如果你需要更强大的类型推断和泛型支持,Psalm可能更适合你。 许多项目也会同时使用这两个工具,以获得更全面的代码分析。
配置PHPStan的严格模式
PHPStan的严格程度通过level参数进行配置,范围从0到9。 level 0是最宽松的,只检查最基本的错误,而level 9是最严格的,会报告几乎所有潜在的问题。
可以通过在phpstan.neon文件中设置level参数来配置PHPStan的严格模式。 例如,要将PHPStan设置为level 7,可以这样做:
parameters:
level: 7
随着项目的发展,可以逐步提高level,以逐步提高代码质量。 建议从较低的level开始,修复发现的问题,然后逐步提高level,直到达到一个平衡点,即代码质量和开发效率之间的最佳折衷。
除了level参数,还可以通过在phpstan.neon文件中设置其他参数来进一步定制PHPStan的行为。 例如,可以使用excludePaths参数排除某些目录或文件,或者使用ignoreErrors参数忽略特定的错误。
parameters:
level: 7
excludePaths:
- tests
- vendor
ignoreErrors:
- '#Call to an undefined method#'
配置Psalm的严格模式
Psalm的严格模式配置相对复杂,但提供了更多的灵活性。 Psalm使用level属性来控制严格程度,范围从1到8,1是最宽松的,8是最严格的。 此外,Psalm还使用config.xsd文件来定义配置选项,允许更精细的控制。
可以在psalm.xml文件中设置level属性来配置Psalm的严格模式。 例如,要将Psalm设置为level 5,可以这样做:
<?xml version="1.0"?>
<psalm
errorLevel="5"
resolveFromConfigFile="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
>
<projectFiles>
<directory name="src"/>
</projectFiles>
</psalm>
与PHPStan类似,也可以使用excludeFiles和ignoreErrors元素来排除文件或忽略特定的错误。
<?xml version="1.0"?>
<psalm
errorLevel="5"
resolveFromConfigFile="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
>
<projectFiles>
<directory name="src"/>
</projectFiles>
<excludeFiles>
<file name="src/SomeLegacyFile.php"/>
</excludeFiles>
<issueHandlers>
<MissingReturnType>
<errorLevel type="suppress">
<referencedClass name="SomeLegacyClass"/>
</errorLevel>
</MissingReturnType>
</issueHandlers>
</psalm>
Psalm的baseline功能也十分强大。 可以通过生成baseline文件,将现有代码中的错误标记为已知错误,然后专注于修复新引入的错误。 这对于大型项目来说非常有用,可以逐步提高代码质量,而不会被大量的现有错误所淹没。
开发自定义规则:PHPStan
PHPStan允许通过开发自定义规则来扩展其功能,以满足特定的项目需求。 自定义规则可以检测到PHPStan内置规则无法检测到的特定错误或代码风格问题。
要开发PHPStan自定义规则,需要创建一个类,实现PHPStanRulesRule接口。 这个接口定义了一个getNodeType()方法,用于指定规则应该应用于哪些类型的AST节点,以及一个processNode()方法,用于处理这些节点并报告错误。
例如,假设我们需要创建一个规则,禁止使用die()函数。 可以这样做:
<?php
namespace MyProjectPHPStanRules;
use PhpParserNode;
use PhpParserNodeExprExit_;
use PHPStanAnalyserScope;
use PHPStanRulesRule;
class NoDieRule implements Rule
{
public function getNodeType(): string
{
return Exit_::class;
}
/**
* @param Exit_ $node
* @param Scope $scope
* @return array<string>
*/
public function processNode(Node $node, Scope $scope): array
{
if ($node->die) {
return [
'使用 die() 函数是不允许的,请使用异常或其他方式来处理错误。',
];
}
return [];
}
}
在这个例子中,NoDieRule类实现了Rule接口,并指定它应该应用于Exit_类型的节点(die()和exit()函数的AST节点)。 processNode()方法检查节点是否是die()函数,如果是,则报告一个错误。
要注册自定义规则,需要在phpstan.neon文件中添加一个services配置:
services:
-
class: MyProjectPHPStanRulesNoDieRule
tags:
- phpstan.rules.rule
开发自定义规则:Psalm
Psalm也允许通过插件来扩展其功能。 Psalm插件可以定义自定义类型、规则和代码转换。
要开发Psalm插件,需要创建一个类,实现PsalmPluginPluginEntryPointInterface接口。 这个接口定义了一个__invoke()方法,用于注册插件提供的功能。
例如,假设我们需要创建一个规则,禁止使用var_dump()函数。 可以这样做:
<?php
namespace MyProjectPsalmPlugin;
use PsalmPluginPluginEntryPointInterface;
use PsalmPluginRegistrationInterface;
class NoVarDumpPlugin implements PluginEntryPointInterface
{
/**
* @param RegistrationInterface $registration
* @return void
*/
public function __invoke(RegistrationInterface $registration, ?SimpleXMLElement $config = null): void
{
$registration->registerHooksFromClass(NoVarDumpHook::class);
}
}
这个插件注册了一个名为NoVarDumpHook的hook。 hook是一个可以拦截Psalm执行过程并执行自定义逻辑的类。
NoVarDumpHook类需要实现PsalmPluginHookAfterStatementAnalysisInterface接口,并实现afterStatementAnalysis()方法。 这个方法在每个语句分析之后被调用,可以在这里检查语句是否包含var_dump()函数,并报告错误。
<?php
namespace MyProjectPsalmPlugin;
use PhpParserNodeExprFuncCall;
use PsalmCodebase;
use PsalmCodeLocation;
use PsalmContext;
use PsalmIssuePluginIssue;
use PsalmIssuesSet;
use PsalmPluginHookAfterStatementAnalysisInterface;
use PsalmStatementsSource;
class NoVarDumpHook implements AfterStatementAnalysisInterface
{
/**
* @param FuncCall $stmt
* @param Context $context
* @param Codebase $codebase
* @param IssuesSet $issues
*
* @return void
*/
public static function afterStatementAnalysis(
PhpParserNodeStmt $stmt,
Context $context,
Codebase $codebase,
IssuesSet $issues,
StatementsSource $source = null
) : void {
if ($stmt instanceof FuncCall
&& $stmt->name instanceof PhpParserNodeName
&& strtolower($stmt->name->parts[0]) === 'var_dump'
) {
$issues->add(
new PluginIssue(
'NoVarDump',
'使用 var_dump() 函数是不允许的,请使用日志或其他方式来调试。',
new CodeLocation($source, $stmt)
),
$source->getSuppressedIssues()
);
}
}
}
要注册Psalm插件,需要在psalm.xml文件中添加一个plugins元素:
<?xml version="1.0"?>
<psalm
errorLevel="5"
resolveFromConfigFile="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
>
<projectFiles>
<directory name="src"/>
</projectFiles>
<plugins>
<pluginClass class="MyProjectPsalmPluginNoVarDumpPlugin"/>
</plugins>
</psalm>
最佳实践
- 逐步提高严格程度: 不要一开始就将PHPStan或Psalm设置为最高级别,而是应该逐步提高严格程度,并修复发现的问题。
- 使用Baseline: 使用Psalm的baseline功能,将现有代码中的错误标记为已知错误,然后专注于修复新引入的错误。
- 编写清晰的错误信息: 在开发自定义规则时,确保编写清晰的错误信息,以便开发者能够理解问题并进行修复。
- 编写单元测试: 为自定义规则编写单元测试,以确保它们能够正确地检测到目标错误。
- 持续集成: 将静态分析集成到持续集成流程中,以便在每次代码提交时自动运行静态分析。
- 团队协作: 确保团队成员都了解静态分析工具的配置和使用方法,并共同维护规则和配置。
总结
今天我们学习了如何配置PHPStan和Psalm的严格模式,以及如何开发自定义规则。 通过合理配置和扩展静态分析工具,可以显著提高PHP代码的质量和可维护性,减少运行时错误,并提升开发效率。 静态分析是现代PHP开发流程中不可或缺的一部分。
更精细的控制是关键
静态分析工具的强大之处在于其可配置性和可扩展性。 通过精细的配置和自定义规则,可以使其更好地适应项目的特定需求,从而最大限度地发挥其价值。