- 开发工具
- 代码质量
- 静态分析
- Lint
- 格式化
【免费下载链接】PHP-CS-Fixer
A tool to automatically fix PHP Coding Standards issues
lowercase_keywords是 PHP-CS-Fixer 在casing(大小写)分类下提供的基础性代码风格规则,其职责是保证代码中出现的 PHP 语言关键字一律使用小写形式。本文以 规则文档 为主体,结合仓库内 Fixer 实现、Tokenizer 底层能力与测试用例,讲解该规则的作用范围、修复原理、边界行为以及在@PSR12、@Symfony等主流规则集中的应用方式,帮助读者理解并安全地使用这条规则。
规则核心语义:PHP 关键字必须小写
该规则的定义(FixerDefinition)非常明确:
PHP keywords MUST be in lower case.
即:代码中所有 PHP 关键字必须使用小写形式。规则注释中说明它对应 PSR-2 规范的 §2.5 小节(Fixer for rules defined in PSR2 ¶2.5,见 LowercaseKeywordsFixer.php),因此它是 PSR-2/PSR-12 风格体系中对关键字大小写的强制性约束。
需要注意,PHP 语言本身对关键字是大小写不敏感的(FOREACH、Foreach、foreach均可被解释器接受),但这会导致同一代码库中出现五花八门的写法,破坏可读性与一致性。lowercase_keywords的目的就是消除这种随意性,统一为规范的小写形式。
官方示例
规则文档给出了如下标准示例(来源:lowercase_keywords.rst),可以看到修复前后的完整差异:
--- Original +++ New <?php - FOREACH($a AS $B) { - TRY { - NEW $C($a, ISSET($B)); - WHILE($B) { - INCLUDE "test.php"; + foreach($a as $B) { + try { + new $C($a, isset($B)); + while($B) { + include "test.php"; } - } CATCH(\Exception $e) { - EXIT(1); + } catch(\Exception $e) { + exit(1); } }示例中FOREACH、AS、TRY、NEW、ISSET、WHILE、INCLUDE、CATCH、EXIT等关键字全部被转换为小写;而变量$a、$B、$C不受影响(它们不是关键字),\Exception类名中的大写E也保持不变。这一点揭示了该规则的一个重要边界:它只改写关键字本身,绝不触碰标识符、类名、变量名和字符串内容。
底层实现:一行代码完成的关键字归一化
LowercaseKeywordsFixer是AbstractFixer的子类(LowercaseKeywordsFixer.php),整个修复逻辑非常精炼,分为两个阶段。
候选判定(isCandidate):快速过滤无关文件
public function isCandidate(Tokens $tokens): bool { return $tokens->isAnyTokenKindsFound(Token::getKeywords()); }在 AbstractFixer::fix 的统一调用链中,只有当文件 token 流里确实存在关键字时,才会进入实际修复流程。这种"候选判定 + 实际修复"的两段式设计是 PHP-CS-Fixer 所有 Fixer 的通用范式:先用开销极低的全量扫描做快速过滤,避免对不含关键字的文件做无谓的遍历。
实际修复(applyFix):逐 token 小写化
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { foreach ($tokens as $index => $token) { if ($token->isKeyword() && !$token->isGivenKind(\T_HALT_COMPILER)) { $tokens[$index] = new Token([$token->getId(), strtolower($token->getContent())]); } } }实现要点有三:
- 基于 token 语义而非文本匹配:规则遍历整个 token 流,通过
$token->isKeyword()判断是否为关键字。这意味着它工作在内核分词结果之上,而非简单的字符串替换,因此天然不会误伤字符串字面量、注释或普通标识符中的大写单词。 - 保留 token 类型:修复时构造新
Token时沿用$token->getId()(token 类型 ID 不变),仅通过strtolower()改写内容,保证后续规则与语法分析仍然正确。 - 显式豁免
T_HALT_COMPILER:__HALT_COMPILER();是 PHP 中用于生成 Phar 档案的特殊结构。测试用例yield ['<?php __HALT_COMPILER();'];(LowercaseKeywordsFixerTest.php)表明该结构保持原样,不参与小写化。从实现看,这是修复循环中唯一的显式例外分支,属于刻意保留的向后兼容行为。
关键字清单:从 Tokenizer 源码看覆盖范围
isKeyword()的实现位于 Token.php,其判定依赖Token::getKeywords()返回的关键字 ID 集合。从 getKeywords() 的源码可以看到,这个集合分为两大类:
- 原生 token 关键字:
T_ABSTRACT、T_ARRAY、T_AS、T_BREAK、T_CALLABLE、T_CASE、T_CATCH、T_CLASS、T_CLONE、T_CONST、T_CONTINUE、T_DECLARE、T_DEFAULT、T_DO、T_ECHO、T_ELSE、T_ELSEIF、T_EMPTY、T_ENDDECLARE、T_ENDFOR、T_ENDFOREACH、T_ENDIF、T_ENDSWITCH、T_ENDWHILE、T_EVAL、T_EXIT、T_EXTENDS、T_FINAL、T_FINALLY、T_FN、T_FOR、T_FOREACH、T_FUNCTION、T_GLOBAL、T_GOTO、T_HALT_COMPILER、T_IF、T_IMPLEMENTS、T_INCLUDE、T_INCLUDE_ONCE、T_INSTANCEOF、T_INSTEADOF、T_INTERFACE、T_ISSET、T_LIST、T_LOGICAL_AND、T_LOGICAL_OR、T_LOGICAL_XOR、T_NAMESPACE、T_NEW、T_PRINT、T_PRIVATE、T_PROTECTED、T_PUBLIC、T_REQUIRE、T_REQUIRE_ONCE、T_RETURN、T_STATIC、T_SWITCH、T_THROW、T_TRAIT、T_TRY、T_UNSET、T_USE、T_VAR、T_WHILE、T_YIELD、T_YIELD_FROM; - 自定义兼容 token(CT/FCT):
CT::T_ARRAY_TYPEHINT、CT::T_CLASS_CONSTANT、CT::T_CONST_IMPORT、CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_*、CT::T_FUNCTION_IMPORT、CT::T_NAMESPACE_OPERATOR、CT::T_USE_LAMBDA、CT::T_USE_TRAIT,以及FCT::T_ENUM、FCT::T_MATCH、FCT::T_READONLY、FCT::T_PRIVATE_SET、FCT::T_PROTECTED_SET、FCT::T_PUBLIC_SET。
其中FCT(Forward Compatibility Tokens,FCT.php)是 PHP-CS-Fixer 为了兼容旧版 PHP 运行环境而引入的"前向兼容 token":当运行环境不支持某 token 时,用负整数值代替原生 token 常量(例如 PHP 8.0 之前的T_MATCH记为-802)。这意味着lowercase_keywords的覆盖范围会随运行环境的 PHP 版本动态扩展——例如在 PHP 8.1+ 环境下运行时会覆盖enum、readonly,在 PHP 8.4+ 环境下会覆盖非对称可见性private(set)等,而无需升级规则本身。
同时需要说明边界:true、false、null等属于原生常量(isNativeConstant 所辖范畴),魔术常量(__CLASS__、__DIR__、__FILE__等,见 getMagicConstants)也不在isKeyword()的判定范围内,它们分别由constant_case、magic_constant_casing等其他 Casing 规则负责。
行为契约:测试用例揭示的官方支持范围
规则文档明确声明(lowercase_keywords.rst):
The test class defines officially supported behaviour. Each test case is a part of our backward compatibility promise.
即测试类是官方支持行为的定义者,每个用例都属于向后兼容承诺的一部分。阅读 LowercaseKeywordsFixerTest.php 可以精确掌握该规则的边界行为:
| 测试场景 | 输入(节选) | 输出 | 说明 |
|---|---|---|---|
| 逻辑运算符 | (1 AND 2) | (1 and 2) | AND属于T_LOGICAL_AND |
| 循环与控制流 | FOREACH(... AS $val) {} | foreach(... as $val) {} | 关键字与变量名分离处理 |
| 字符串内容 | echo "GOOD AS NEW"; | 原样保留 | 字符串内的"关键字"不受影响 |
| 类常量访问 | X::ClASs | X::class | 混合大小写也会被归一 |
| 箭头函数 | FN() => true | fn() => true | PHP 7.4+ 的fn |
| Phar 结构 | __HALT_COMPILER(); | 原样保留 | 唯一显式豁免 |
| match 表达式(PHP 8.0+) | MATCH (1) {...} | match (1) {...} | 覆盖T_MATCH |
| 构造器属性提升(PHP 8.0+) | PUBLIC float $x | public float $x | 提升可见性关键字 |
| readonly(PHP 8.1+) | public READONLY string $prop | public readonly string $prop | 含rEADONLY等混合写法 |
| enum(PHP 8.1+) | ENUM Suit {...} | enum Suit {...} | 覆盖FCT::T_ENUM |
| 非对称可见性(PHP 8.4+) | PUBLIC(SET) Bar $a | public(set) Bar $a | 覆盖FCT::T_PUBLIC_SET等 |
这些用例按 PHP 版本分层(#[RequiresPhp]注解与@requires标注),充分说明该规则对较新语言特性同样提供关键字小写化支持,且行为随运行版本自适应。
规则集传播链:一条规则如何进入主流风格体系
lowercase_keywords没有配置项(属于无参规则,直接启用即可),它主要通过规则集(Rule Set)被引入。规则文档列出了它所属的全部规则集(lowercase_keywords.rst),结合 src/RuleSet/Sets 目录下的集合定义源码,可以还原出完整的传播链:
@PSR2:在 PSR2Set.php 中直接启用'lowercase_keywords' => true,这是该规则的源头;@PSR12:通过'@PSR2' => true继承(PSR12Set.php);@PER-CS1x0:通过'@PSR12' => true继承(PERCS1x0Set.php);@PER-CS2x0:通过'@PER-CS1x0' => true继承(PERCS2x0Set.php);@PER-CS3x0:通过'@PER-CS2x0' => true继承(PERCS3x0Set.php);@Symfony:通过'@PER-CS3x0' => true继承(SymfonySet.php);@PhpCsFixer:通过'@Symfony' => true继承(PhpCsFixerSet.php)。
此外文档还列出@PER、@PER-CS1.0、@PER-CS2.0、@PER-CS3.0四个标记为(deprecated)的旧命名规则集,对应新的@PER-CS、@PER-CS1x0、@PER-CS2x0、@PER-CS3x0命名体系(如 PERSet.php 实现了DeprecatedRuleSetDefinitionInterface)。
由此可以看出:只要项目使用了@PSR2及以上任何一级主流规则集,就会自动获得关键字小写化的保障。各规则集对应的完整规则清单可查阅 doc/ruleSets 目录(如 PSR2.rst、PSR12.rst、Symfony.rst)。
实战使用:如何启用与验证
命令行方式
无需修改任何配置文件,直接通过命令行指定规则运行(以仓库根目录下的 php-cs-fixer 可执行入口为例):
php php-cs-fixer fix path/to/your/file.php --rules=lowercase_keywords配合--dry-run与--diff可以先预览将要发生的修改而不落盘:
php php-cs-fixer fix path/to/your/dir --rules=lowercase_keywords --dry-run --diff配置文件方式
在.php-cs-fixer.php(或.php-cs-fixer.dist.php)配置文件中,既可以单独启用,也可以借助规则集批量启用:
<?php return (new PhpCsFixer\Config()) ->setRules([ 'lowercase_keywords' => true, // 单独启用 // 或直接使用 '@PSR12' => true 等规则集整体引入 ]) ->setFinder(PhpCsFixer\Finder::create()->in(__DIR__));运行测试验证行为
仓库为每条规则配备了完整的单元测试。针对本规则,可运行以下命令验证其官方支持行为:
php vendor/bin/phpunit tests/Fixer/Casing/LowercaseKeywordsFixerTest.php测试通过即代表当前 PHP 环境下规则行为符合官方向后兼容承诺。需要注意的是,match、readonly、enum、非对称可见性等用例带有 PHP 版本要求注解,低版本环境下这些用例会被自动跳过,这是预期行为而非测试失败。
与其他 Casing 规则的协同
lowercase_keywords属于 src/Fixer/Casing 目录下的 10 个大小写类规则之一,它与同族的其他规则各司其职、互不重叠:
constant_case:负责true/false/null等原生常量的写法归一;magic_constant_casing:负责__CLASS__、__DIR__等魔术常量的大小写;native_function_casing、native_type_declaration_casing:分别处理内置函数名与内置类型声明的大小写;class_reference_name_casing、lowercase_static_reference:处理类引用与self/static/parent等静态引用;integer_literal_case、magic_method_casing:处理整数字面量与魔术方法名。
在实际项目中,通常无需单独操心该规则——@PSR12或@Symfony等规则集已将其纳入,配合上述同族规则即可实现完整的"大小写一致性"治理。
小结
lowercase_keywords是 PHP-CS-Fixer 中最基础也最安全的规则之一:它以 token 语义而非文本匹配为核心,将全部 PHP 关键字(含fn、match、readonly、enum及set可见性等新特性关键字)归一为小写,同时严格排除字符串、注释、标识符与__HALT_COMPILER结构。通过@PSR2向上逐级传播,它覆盖了@PSR12、@PER-CS*、@Symfony、@PhpCsFixer等主流规则集,是任何 PHP 项目代码风格基线中不可或缺的一环。
- 开发工具
- 代码质量
- 静态分析
- Lint
- 格式化
【免费下载链接】PHP-CS-Fixer
A tool to automatically fix PHP Coding Standards issues
相关推荐
PHP-CS-Fixer `lowercase_cast` 规则详解:强制类型转换关键字小写化的原理与实践
PHP CS Fixer lowercase_cast 规则详解:强制类型转换关键字小写化的原理与实践 导读 lowercase_cast 是 PHP CS F
开发工具代码质量静态分析Lint格式化PHP-CS-Fixer `@PSR1` 规则集详解:encoding 与 full_opening_tag 的强制规范与实现原理
PHP CS Fixer @PSR1 规则集详解:encoding 与 full_opening_tag 的强制规范与实现原理 @PSR1 是 PHP CS F
开发工具代码质量静态分析Lint格式化PHP-CS-Fixer 规则详解:single_import_per_statement 强制一个 use 关键字对应一条声明
PHP CS Fixer 规则详解:single_import_per_statement 强制一个 use 关键字对应一条声明 导读 single_impor
开发工具代码质量静态分析Lint格式化
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考