ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

PHP-CS-Fixer `strict_comparison` 规则深度解析:将松散比较强制改为严格比较

PHP-CS-Fixer `strict_comparison` 规则深度解析:将松散比较强制改为严格比较 PHP-CS-Fixerstrict_comparison规则深度解析将松散比较强制改为严格比较【免费下载链接】PHP-CS-FixerA tool to automatically fix PHP Coding Standards issues项目地址: https://gitcode.com/gh_mirrors/ph/PHP-CS-Fixer导读strict_comparison是 PHP-CS-Fixer 中Strict严格模式规则族的一员其唯一职责是把代码中的松散比较运算符/!含别名写法统一替换为严格比较运算符/!从而消除 PHP 弱类型比较带来的隐式类型转换隐患。本文以官方规则文档 doc/rules/strict/strict_comparison.rst 为主体结合 StrictComparisonFixer 源码、单元测试与集成测试用例完整讲解该规则的修复行为、RISKY 风险、执行优先级、所属规则集及实战接入方式帮助你在启用前充分评估其对既有代码行为的影响。一、规则概览Comparisons should be strictPHP-CS-Fixer 官方对strict_comparison的定义只有一句话Comparisons should be strict.比较应当严格。这句话背后对应的是一个确定性的、机械式的 token 替换行为凡是源码中出现松散相等/不等比较的 token都会被改写为对应的严格比较 token不涉及任何语义分析、类型推断或条件判断。官方文档给出的修复示例官方文档中的 Code Sample 如下--- Original New ?php -$a 1 $b; $a 1 $b;可以看到被直接改写为。这个示例同时也提示了一个细节该规则只负责替换比较运算符本身不会顺带调整运算符两侧的空格示例中1 $b修复后仍是1 $b左空格缺失的问题需要交给binary_operator_spaces之类的规则处理详见后文“执行优先级”一节。规则属性一览属性值依据Fixer 类PhpCsFixer\Fixer\Strict\StrictComparisonFixersrc/Fixer/Strict/StrictComparisonFixer.php所属命名空间PhpCsFixer\Fixer\Strict同目录下另有DeclareStrictTypesFixer、StrictParamFixer两个兄弟规则是否 RISKY是见下节执行优先级38getPriority()返回值是否可配置否无配置选项类中未实现ConfigurableFixerInterface仅继承 AbstractFixer所属规则集PhpCsFixer:riskydoc/ruleSets/PhpCsFixerRisky.rst二、为什么这是 RISKY 规则行为变更风险官方文档在规则描述后专门设置了Warning段落This rule is RISKYChanging comparisons to strict might change code behaviour.将比较改为严格模式可能改变代码行为。这一点在源码中也有直接印证。StrictComparisonFixer::isRisky()固定返回true见 StrictComparisonFixer.php 的isRisky()方法同时getDefinition()构造FixerDefinition时传入的第四个参数风险描述正是 Changing comparisons to strict might change code behaviour.。风险的本质PHP 弱类型比较的隐式转换之所以危险是因为在 PHP 中会触发隐式类型转换而会先比较类型再比较值var_dump(0 foo); // true字符串被转换为 0 var_dump(0 foo); // false var_dump(1 1); // true var_dump(1 1); // false var_dump(null false); // true var_dump(null false); // false一旦代码中确实依赖了这类弱类型语义例如strpos($haystack, $needle) 0判断字符串是否以某前缀开头被strict_comparison机械改写为之后运行结果就可能截然不同。因此该规则默认不会启用必须显式配置或在 RISKY 规则集中开启官方在 PhpCsFixerRisky.rst 的规则集说明中同样强调“使用它可能导致代码逻辑与行为发生变化请谨慎使用并在合入代码库之前审查变更。”建议启用strict_comparison前先使用php-cs-fixer fix --dry-run --diff查看将要发生的改动逐条人工评估每一处/!是否真的可以安全升级为/!同时结合单元测试覆盖确认修复后行为未发生预期外的变化。三、修复行为详解源码级实现原理StrictComparisonFixer的完整实现非常精简全部逻辑集中在三个方法中3.1 替换映射表 FIX_MAPprivate const FIX_MAP [ \T_IS_EQUAL [\T_IS_IDENTICAL, ], \T_IS_NOT_EQUAL [\T_IS_NOT_IDENTICAL, !], ];这是整个规则的“心脏”两张 token 级映射原始 tokenPHP 常量替换后 tokenPHP 常量T_IS_EQUALT_IS_IDENTICAL!T_IS_NOT_EQUAL!T_IS_NOT_IDENTICAL值得注意PHP 的写法在词法分析阶段同样被归类为T_IS_NOT_EQUAL因此它也会被一并替换为!这一点由单元测试明确覆盖见下文。3.2 候选检测 isCandidate()public function isCandidate(Tokens $tokens): bool { return $tokens-isAnyTokenKindsFound([\T_IS_EQUAL, \T_IS_NOT_EQUAL]); }PHP-CS-Fixer 的 Fixer 在正式应用前会先调用isCandidate()做快速筛查只有当文件 token 流中确实存在T_IS_EQUAL或T_IS_NOT_EQUAL时该规则才会进入后续的applyFix()阶段。这种“先筛查、后修复”的机制可以显著提升大批量文件扫描时的性能。3.3 核心修复 applyFix()protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { foreach ($tokens-findGivenKind([\T_IS_EQUAL, \T_IS_NOT_EQUAL]) as $kind $kindTokens) { foreach ($kindTokens as $index $token) { \assert(isset(self::FIX_MAP[$kind])); $newToken self::FIX_MAP[$kind]; $tokens[$index] new Token($newToken); } } }流程一目了然通过Tokens::findGivenKind()一次性找出文件中所有T_IS_EQUAL/T_IS_NOT_EQUALtoken对每个命中位置从FIX_MAP取出目标 token 定义token 类型 文本原地用new Token(...)替换。由于 PHP-CS-Fixer 的 Tokenizer 已经将字符串字面量、注释等内容正确切分为独立的 token字符串与注释中的文本不会受到任何影响——这是 token 级修复相比纯正则替换最本质的优势。3.4 为什么没有任何配置项StrictComparisonFixer没有实现ConfigurableFixerInterface因此不像binary_operator_spaces那样支持参数化配置。它是“全有或全无”的布尔式规则开启即替换所有命中的比较运算符。从 AbstractFixer 的构造函数可以看到只有实现了ConfigurableFixerInterface的 Fixer 才会尝试configure([])初始化配置本规则走不到这一步。四、单元测试官方承诺的行为边界官方文档在 References 一节明确写道The test class defines officially supported behaviour. Each test case is a part of our backward compatibility promise.测试类定义了官方支持的行为每个测试用例都是我们向后兼容承诺的一部分。也就是说tests/Fixer/Strict/StrictComparisonFixerTest.php 中的每一个用例都是官方承诺未来版本不会破坏的行为契约。当前共 4 个用例yield [?php $a $b;, ?php $a $b;]; // → yield [?php $a ! $b;, ?php $a ! $b;]; // ! → ! yield [?php $a ! $b;, ?php $a $b;]; // → !别名写法同样处理 yield [?php echo $a $b;]; // 字符串内的 不被触碰无 input期望原样输出逐一解读用例输入期望输出说明#1$a $b;$a $b;基本相等比较#2$a ! $b;$a ! $b;基本不等比较#3$a $b;$a ! $b;PHP 的是!的别名写法同样被归一化为!#4echo $a $b;原样输出双引号字符串内的文本不受影响注意该用例没有 input表示期望结果中不出现任何替换最后一个用例尤其值得注意它验证了 token 级替换的安全性——字符串插值内容$a $b中的只是普通文本不会因findGivenKind被误判为运算符。这正体现了 PHP-CS-Fixer 基于 Tokenizer 的修复架构相较于文本正则替换的可靠性。五、执行优先级与相邻规则的协作StrictComparisonFixer通过getPriority()返回优先级38并声明Must run before BinaryOperatorSpacesFixer, ModernizeStrposFixer.必须在binary_operator_spaces与modernize_strpos之前运行。仓库中的两个集成测试用例正是对这一契约的验证5.1 与modernize_strpos协作tests/Fixtures/Integration/priority/strict_comparison,modernize_strpos.test// INPUT $x strpos($foo, foo) 0; // EXPECT $x str_starts_with($foo, foo) ;modernize_strpos会把strpos($x, $y) 0这种惯用法重写为语义等价且无歧义的str_starts_with()。由于strict_comparison优先级更高先执行它会先把变成modernize_strpos随即识别出strpos(...) 0并完成现代化改写。如果顺序颠倒被改成后modernize_strpos的匹配模式就可能失效。5.2 与binary_operator_spaces协作tests/Fixtures/Integration/priority/strict_comparison,binary_operator_spaces.test// INPUT $a $b $c; $d $e $f; // RULESET {strict_comparison: true, binary_operator_spaces: {operators:{:align_single_space}}} // EXPECT $a $b $c; $d $e $f;strict_comparison先完成→的替换随后binary_operator_spaces按照配置对运算符做对齐与空格规范化。若反过来先做空格处理则空格规则针对的是而非最终的结果会不符合预期。实践提示由于存在这种优先级依赖建议在自定义.php-cs-fixer.php配置中让strict_comparison与binary_operator_spaces、modernize_strpos同时启用或使用包含它们的官方规则集由 FixerFactory 依据getPriority()自动排序避免人为配置顺序导致修复结果不一致。六、所属规则集与启用方式6.1 唯一所属规则集PhpCsFixer:risky官方文档指出该规则只属于一个规则集PhpCsFixer:risky对应文档 doc/ruleSets/PhpCsFixerRisky.rst在 PhpCsFixerRisky.rst 的 Rules 列表中strict_comparison与declare_strict_types、strict_param、void_return等规则并列出现。该规则集是 PHP-CS-Fixer 团队推荐的“高度意见化”集合内部再扩展自PER-CS:risky与Symfony:risky。6.2 三种启用方式方式一使用完整 RISKY 规则集php php-cs-fixer fix --rulesPhpCsFixer:risky path/to/code方式二基于规则集做增量在.php-cs-fixer.php配置文件中?php return (new PhpCsFixer\Config()) -setRules([ PhpCsFixer:risky true, // 如需临时关闭某条规则可显式置 false // strict_comparison false, ]) -setFinder( PhpCsFixer\Finder::create() -in(__DIR__) -exclude(vendor) ) ;方式三单独启用只开这一条规则-setRules([ strict_comparison true, ])由于规则无配置选项值只需写true无需也不能传入参数数组。6.3 启用前的审查清单运行php-cs-fixer fix --dry-run --diff --rulesstrict_comparison输出全部将被修改的比较运算逐个检查是否有依赖弱类型比较的隐式转换如与0、false、null、空字符串的比较检查字符串前缀/包含判断等惯用法确认是否需要配合modernize_strpos等规则改写而非简单升级为在 CI 中先以 dry-run 形式观察若干次提交确认修复结果稳定后再正式合入。七、与同族规则的协同strict_comparison位于 src/Fixer/Strict 目录下与另外两条 Strict 家族规则共同构成“严格化”改造组合拳规则作用文档strict_comparison比较运算符/!/→/!strict_comparison.rststrict_param为array_keys、array_search、base64_decode、in_array、mb_detect_encoding等函数补上$strict true参数strict_param.rstdeclare_strict_types在文件头部插入declare(strict_types1);让函数调用进入严格类型模式declare_strict_types.rst三者的共同点是全部属于 RISKY 规则且都指向同一目标消除 PHP 弱类型行为带来的隐蔽 Bug。例如 StrictParamFixer 中in_array($b, $c)会被改写成in_array($b, $c, true)与strict_comparison的化在语义上互为表里。如果你的项目正在推行严格的类型安全策略这三条规则值得作为一个整体来评估引入。八、总结strict_comparison是 PHP-CS-Fixer 中最“小而纯粹”的规则之一一张两张 token 的映射表、一次findGivenKind遍历即可完成全文件的比较运算符严格化。但它的简单性背后是明确的行为变更风险——任何都可能承载着开发者有意或无意依赖的弱类型语义。核心要点回顾修复范围→!/→!字符串与注释中的文本不受影响风险等级RISKYisRisky()恒为true默认不启用优先级38必须先于binary_operator_spaces、modernize_strpos执行规则集仅PhpCsFixer:risky行为契约StrictComparisonFixerTest 中的 4 个用例构成官方向后兼容承诺最佳实践启用前务必--dry-run --diff审查配合单测覆盖必要时与modernize_strpos等规则组合使用以消除惯用法风险。【免费下载链接】PHP-CS-FixerA tool to automatically fix PHP Coding Standards issues项目地址: https://gitcode.com/gh_mirrors/ph/PHP-CS-Fixer创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表