- 开发工具
- 代码质量
- 静态分析
- Lint
- 格式化
【免费下载链接】PHP-CS-Fixer
A tool to automatically fix PHP Coding Standards issues
本文围绕 PHP-CS-Fixer 中的native_type_declaration_casing规则展开,深入讲解它如何将INT、CALLABLE、BOOL等大小写不规范的原生类型声明统一为小写形式,覆盖可修复的场景、被刻意排除的边界情况、底层 Token 处理原理,以及如何在@Symfony/@PhpCsFixer规则集中启用它。读完本文,你将能够在实际项目中安全地启用该规则,并理解它为什么不会误伤类名、常量名与命名空间片段。
规则概述:什么是 native_type_declaration_casing
native_type_declaration_casing是 PHP-CS-Fixer 中位于 Casing(大小写)分类下的一个内置规则,官方定义为:原生类型声明(Native type declarations)应使用正确的大小写("Native type declarations should be used in the correct case.")。
PHP 是大小写不敏感的语言,因此下面两种写法在运行时完全等价:
function Foo(INT $bar): VOID {} // 可以运行,但风格混乱 function foo(int $bar): void {} // 符合主流编码规范该规则的作用就是扫描代码中的类型声明位置,把所有大写的原生类型关键字统一改写成小写,从而让代码风格保持一致。它对应源码为 NativeTypeDeclarationCasingFixer.php,测试覆盖见 NativeTypeDeclarationCasingFixerTest.php。
需要特别强调的是:这里只处理原生(内置)类型,例如int、string、array等。你自定义的类名、接口名、命名空间别名等不在处理范围内——它们通常需要遵循PascalCase等其他约定,不应被强行小写。
官方文档示例:两种典型场景
规则文档(native_type_declaration_casing.rst)给出了两个 diff 形式的示例,直观展示了修复前后变化。
示例一:函数参数与返回类型
--- Original +++ New <?php class Bar { - public function Foo(CALLABLE $bar): INT + public function Foo(callable $bar): int { return 1; } }这里同时处理了参数类型CALLABLE和返回类型INT,分别改为callable与int。
示例二:类常量类型声明(PHP 8.3+)
--- Original +++ New <?php class Foo { - const INT BAR = 1; + const int BAR = 1; }PHP 8.3 起支持为类常量声明类型,该规则同样会将其中的原生类型关键字规范化。
支持修复的类型清单:按 PHP 版本演进
从源码 NativeTypeDeclarationCasingFixer.php 可以看到,规则内部维护了一个$types集合,只有命中该集合的类型才会被改写。集合内容与 PHP 版本强相关,完整清单如下:
| 类型关键字 | 引入版本 | 说明 |
|---|---|---|
self | PHP 5.0 | 指向当前类 |
array | PHP 5.1 | 数组 |
callable | PHP 5.4 | 可调用类型 |
bool/float/int/string | PHP 7.0 | 标量类型 |
iterable/void | PHP 7.1 | 可迭代 / 无返回值 |
object | PHP 7.2 | 对象类型 |
static | PHP 8.0 | 仅作返回类型 |
mixed | PHP 8.0 | 混合类型 |
false/null | PHP 8.0 | 联合返回类型中可用 |
never | PHP 8.1 | 仅作返回类型 |
true | PHP 8.2 | 独立类型(true-type RFC) |
false/null | PHP 8.2 | 独立类型(RFC) |
代码中的判定逻辑是:
$this->types = [ 'array' => true, 'bool' => true, 'callable' => true, 'float' => true, 'int' => true, 'iterable' => true, 'object' => true, 'parent' => true, 'self' => true, 'static' => true, 'string' => true, 'void' => true, ]; if (\PHP_VERSION_ID >= 8_00_00) { /* false, mixed, null */ } if (\PHP_VERSION_ID >= 8_01_00) { $this->types['never'] = true; } if (\PHP_VERSION_ID >= 8_02_00) { $this->types['true'] = true; }可以看到,false、null等关键字在低版本 PHP 中不是类型而是普通标识符,因此规则会根据当前运行环境(PHP_VERSION_ID)动态决定是否把它们视为类型。例如在 PHP 8.0 以下环境中,MIXED会被当作普通类名而保持不变——测试类中的testFixPre80用例(NativeTypeDeclarationCasingFixerTest.php)专门验证了这一点:PHP < 8.0 时private MIXED $m;不会被改写。
实际修复效果与边界场景
结合测试类 NativeTypeDeclarationCasingFixerTest.php 中的大量用例,可以归纳出规则的实际行为边界。先看它会修复的场景:
// 函数参数与返回类型 function Foo(BOOL $a, FLOAT $b, INT $c, STRING $d): INT {} // 修复为:function Foo(bool $a, float $b, int $c, string $d): int {} // 可空类型 ?type function Foo(?INT $A): VOID {} // 修复为:function Foo(?int $A): void {} // 类属性类型 class Foo { private BOOL $c = false; } // 修复为:class Foo { private bool $c = false; } // 联合类型(PHP 8.0+) function foo(INT|BOOL $x): INT|BOOL {} // 修复为:function foo(int|bool $x): int|bool {} // 交叉类型、析取范式(DNF)类型 private (A&B)|INT|D $d5; // INT 被修复为 int,(A&B) 中的类名不受影响 // 构造器属性提升(promoted properties) public function __construct(public INT $i, ...) {} // 修复为:public function __construct(public int $i, ...) {} // 闭包与箭头函数 return fn (CALLABLE $c): INT => 1; // 修复为:return fn (callable $c): int => 1; // readonly 属性(PHP 8.1+) private readonly ARRAY $ax; // 修复为 private readonly array $ax; // 类常量类型声明(PHP 8.3+) const INT SOME_INT = 3; // 修复为 const int SOME_INT = 3;再看它不会修复的场景,这些正是避免误伤的关键:
- 类名与命名空间片段:
INTEGER、Foo\INT\B、String\A等不会被改写,因为INTEGER不是原生类型关键字,且A\INT\B中的INT是命名空间段; - 常量名称:
const INT = "A";中INT是常量名而非类型,保持不变; - 属性名:
private $INT = 1;中INT是属性名,保持不变; - 动态属性访问:
$this->Object->doBar();中Object是属性名,保持不变; - switch case 与比较表达式:
case True:、True === $x等中True/False是常量字面量,不属于类型声明,保持不变; - 全局常量:类外的
const INT = "A";不会被修改。
底层实现原理:如何精准定位"类型声明"位置
从源码结构看,规则通过三层防线避免误伤,这也是理解该规则稳健性的关键。
第一步:isCandidate 快速筛选
isCandidate()(源码)在遍历 Token 前先做一次快速判断,只有在以下情况才进入正式修复流程:
- 存在
T_FUNCTION或T_FN(普通函数、方法、闭包、箭头函数); - 或存在类/接口/trait/enum 关键字(
Token::getClassyTokenKinds())且同时存在T_STRING; - 或 PHP >= 8.3 时存在
T_CONST且上下文中有类声明(对应类常量类型声明场景)。
这个设计保证了绝大多数无关文件会被直接跳过,提升整体处理性能。
第二步:类型白名单校验
applyFix()(源码)遍历所有 Token,对每个 Token 执行strtolower后检查是否命中$this->types白名单。大小写已经正确的 Token 直接跳过($content === $lowercaseContent时continue)。
第三步:前后 Token 语境判定
这是最关键的一步。即使命中了白名单,还要检查该 Token 的前一个有意义 Token(prev)和后一个有意义 Token(next):
- 如果前一个是
=、T_CASE、T_OBJECT_OPERATOR(->)、T_DOUBLE_COLON(::)、T_NS_SEPARATOR(\),则跳过——这说明当前 Token 很可能是常量名、属性名或命名空间段; - 如果后一个是
=或T_NS_SEPARATOR,同样跳过——const INT = 1或Foo\INT这类场景被排除; - 只有前一个是
T_CONST、CT::T_NULLABLE_TYPE(?)、CT::T_TYPE_ALTERNATION(|)、CT::T_TYPE_COLON(:返回类型分隔符),或者后一个是T_VARIABLE($a)、CT::T_TYPE_ALTERNATION(|)时,才确认当前 Token 确实处于类型声明位置,执行小写替换。
这套语境判定正是规则"不误伤"的核心保障。测试类中大量"do not fix"用例(如public const ?INT\A X=C;中INT保持不动、const ?BAR B = null;中BAR保持不动)验证了这些边界条件(测试用例)。
如何启用该规则
native_type_declaration_casing没有配置选项(非可配置规则),启用方式只有"开启或关闭"两种。
方式一:通过规则集启用
根据文档与规则集源码,该规则属于以下两个内置规则集:
- @Symfony:在 SymfonySet.php 中以
'native_type_declaration_casing' => true显式启用,对应规则集文档 Symfony.rst; - @PhpCsFixer:
PhpCsFixerSet组合了@Symfony等规则集,因此同样包含该规则。
在.php-cs-fixer.php配置文件中直接使用规则集即可:
<?php return (new PhpCsFixer\Config()) ->setRules([ '@Symfony' => true, ]) ;方式二:单独启用
如果不想引入整个规则集,可以只开启这一条规则:
<?php return (new PhpCsFixer\Config()) ->setRules([ 'native_type_declaration_casing' => true, ]) ;运行命令
# 检查模式下查看哪些文件会被修改 php php-cs-fixer fix --dry-run --diff --rules=native_type_declaration_casing . # 直接应用修复 php php-cs-fixer fix --rules=native_type_declaration_casing .注意事项与兼容性提示
- 运行环境决定能力边界:规则会根据当前 PHP 版本动态启用
false、null、mixed、never、true等类型的修复。例如在 PHP 8.0 环境运行,never(PHP 8.1)与true(PHP 8.2)不会被识别为类型关键字,相关代码会保持原样;类常量类型声明的修复(const INT FOO = 6;)仅在 PHP >= 8.3 时生效(对应VersionSpecification(8_03_00)版本限定示例,见 源码)。 - 与
native_function_casing的区别:native_function_casing处理的是函数调用(如STRLEN改为strlen),而本规则只处理类型声明,两者作用域不同。 - 类常量类型场景的兼容性:PHP 8.3 之前类常量不支持类型声明,因此示例二中的代码在旧版本上是语法错误——该规则只是将写法规范化为小写,并不会改变 PHP 版本的兼容性边界。
- 向后兼容承诺:官方在规则文档末尾明确指出,测试类(NativeTypeDeclarationCasingFixerTest.php)中定义的每个测试用例都是官方支持的正式行为,属于向后兼容承诺的一部分。如果你的代码依赖规则对某个边界场景的处理,可以参考该测试类确认预期行为。
小结
native_type_declaration_casing是一个简单但覆盖面很广的规范化规则:它将参数类型、返回类型、属性类型、联合/交叉类型、可空类型以及 PHP 8.3 类常量类型中的原生类型关键字统一为小写,同时通过精确的 Token 语境判定,确保类名、常量名、属性名和命名空间段完全不受影响。配合@Symfony或@PhpCsFixer规则集使用,可以低成本地消除代码库中类型声明大小写混用的风格问题。
- 开发工具
- 代码质量
- 静态分析
- Lint
- 格式化
【免费下载链接】PHP-CS-Fixer
A tool to automatically fix PHP Coding Standards issues
相关推荐
PHP-CS-Fixer `native_function_type_declaration_casing` 规则详解:函数原生类型声明大小写规范化与迁移指南
PHP CS Fixer native_function_type_declaration_casing 规则详解:函数原生类型声明大小写规范化与迁移指南 本篇
开发工具代码质量静态分析Lint格式化PHP-CS-Fixer `blank_line_after_namespace` 规则详解:namespace 声明后的空行规范化
PHP CS Fixer blank_line_after_namespace 规则详解:namespace 声明后的空行规范化 导读 blank_line_a
开发工具代码质量静态分析Lint格式化PHP-CS-Fixer 的 phpdoc_to_param_type 规则:将 @param 注解迁移为原生参数类型声明
PHP CS Fixer 的 phpdoc_to_param_type 规则:将 @param 注解迁移为原生参数类型声明 导读 phpdoc_to_param
开发工具代码质量静态分析Lint格式化
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考