- 开发工具
- 代码质量
- 静态分析
- Lint
- 格式化
【免费下载链接】PHP-CS-Fixer
A tool to automatically fix PHP Coding Standards issues
curly_braces_position是 PHP-CS-Fixer 中负责统一花括号摆放位置的可配置规则:它允许你针对类、函数、匿名函数、匿名类以及if/for/switch等控制结构,分别指定开括号是放在“签名末尾同一行”还是“另起一行”。本文以该规则的官方文档为主体,结合 CurlyBracesPositionFixer 与 BracesPositionFixer 的实现源码、CurlyBracesPositionFixerTest 测试用例以及相关规则集配置,完整讲解全部 7 个配置项、8 个官方示例、底层 Token 级工作原理与迁移到braces_position的注意事项。
规则定位与弃用警告
该规则的核心目标一句话即可概括:花括号必须按照配置的方式摆放("Curly braces must be placed as configured.")。它属于可配置规则(CONFIGURABLE),配置入口覆盖 7 个选项:allow_single_line_anonymous_functions、allow_single_line_empty_anonymous_classes、anonymous_classes_opening_brace、anonymous_functions_opening_brace、classes_opening_brace、control_structures_opening_brace、functions_opening_brace。
需要特别注意的是,curly_braces_position已被标记为 DEPRECATED(弃用),并将在下一个大版本 4.0 中移除。官方文档明确建议:应当改用braces_position规则。两者的功能与配置项完全一致——CurlyBracesPositionFixer在实现上本身就是对BracesPositionFixer的薄封装(见下文“代理架构”),因此迁移时只需把规则名从curly_braces_position换成braces_position,原有配置数组可以原样保留。
底层实现:代理 Fixer 架构与 Token 级处理
从源码结构看,CurlyBracesPositionFixer继承自AbstractProxyFixer(见 src/AbstractProxyFixer.php),并在构造时创建了一个BracesPositionFixer实例作为真正的执行者:
// src/Fixer/Basic/CurlyBracesPositionFixer.php final class CurlyBracesPositionFixer extends AbstractProxyFixer implements ConfigurableFixerInterface, DeprecatedFixerInterface, WhitespacesAwareFixerInterface { private BracesPositionFixer $bracesPositionFixer; public function __construct() { $this->bracesPositionFixer = new BracesPositionFixer(); parent::__construct(); } // ... }该类同时实现了DeprecatedFixerInterface,并通过getSuccessorsNames()声明后继者:
public function getSuccessorsNames(): array { return [ $this->bracesPositionFixer->getName(), ]; }也就是说,curly_braces_position的配置解析(createConfigurationDefinition())、规则定义(getDefinition())乃至修复逻辑(applyFix())全部委托给BracesPositionFixer,弃用的只是规则名称本身。
修复算法:识别结构、定位开括号、搬移 Token
真正的修复逻辑位于 BracesPositionFixer::applyFix(),它逐 Token 遍历,按结构类型分类处理:
- 类声明(含匿名类):遇到 classy token 后,用
getNextTokenOfKind($index, ['{'])找到开括号,再通过TokensAnalyzer->isAnonymousClass()区分匿名类与具名类,分别套用anonymous_classes_opening_brace与classes_opening_brace两个配置。 - 函数/方法/匿名函数:遇到
T_FUNCTION后定位开括号;若TokensAnalyzer->isLambda()判定为闭包,则走anonymous_functions_opening_brace,否则走functions_opening_brace。 - 控制结构:通过
CONTROL_STRUCTURE_TOKENS常量匹配T_DECLARE、T_DO、T_ELSE、T_ELSEIF、T_FINALLY、T_FOR、T_FOREACH、T_IF、T_WHILE、T_TRY、T_CATCH、T_SWITCH以及FCT::T_MATCH(match 表达式),然后寻找参数括号的结束位置并取其后第一个有意义的 Token 作为开括号,套用control_structures_opening_brace。 - 属性钩子(property hooks):源码中还处理了
CT::T_PROPERTY_HOOK_BRACE_OPEN相关逻辑,这是 PHP 8.4 属性钩子语法支持的一部分;从代码分支看,多行属性钩子的开括号位置同样归入控制结构配置($positionOption = 'control_structures_opening_brace';)。
在确定开括号目标位置后,fixer 会执行两类核心操作:
- 保证
{后的换行:当花括号体内应换行($addNewlinesInsideBraces为真)且开括号后没有换行、没有同行注释、后跟的也不是?>时,注入换行符 + 当前缩进。 - 移动开括号本身:根据配置把
{之前的空白替换成' '(同一行)或换行符 + 缩进(下一行),必要时连同相邻注释一起搬移;代码中专门处理了/* */、//注释位于开括号前后的各种组合(对应测试用例中的'open brace preceded by comment and whitespace'等场景)。
一个值得注意的细节是:next_line_unless_newline_at_signature_end选项在搬移时还会回溯参数括号,如果签名本身已经是多行的(例如参数换行书写、返回类型跨行),则回退为单个空格而不是强制换行——这正是该选项名称中unless_newline_at_signature_end的语义,下面会结合示例说明。
执行优先级(Priority)
getPriority()返回-2,并声明了与其他规则的先后关系(见 BracesPositionFixer.php):
- 必须运行在
SingleLineEmptyBodyFixer、StatementIndentationFixer之前; - 必须运行在
ControlStructureBracesFixer、MultilinePromotedPropertiesFixer、NoMultipleStatementsPerLineFixer之后。
仓库中的集成测试文件 curly_braces_position,single_line_empty_body.test 验证了这一协作:输入function foo()\n{\n},先由curly_braces_position处理花括号位置,再由single_line_empty_body压缩成function foo() {}。同目录下还有control_structure_braces,curly_braces_position.test、curly_braces_position,statement_indentation.test、no_multiple_statements_per_line,curly_braces_position.test等组合用例,说明该规则与花括号补充、语句缩进、单行多语句等规则存在紧密的先后依赖。
配置项全解
该规则的所有配置均通过BracesPositionFixer::createConfigurationDefinition()(源码位置)声明,使用FixerOptionBuilder定义类型、允许值与默认值。下表汇总 7 个选项:
| 配置项 | 类型 | 允许值 | 默认值 | 默认值(future-mode) |
|---|---|---|---|---|
allow_single_line_anonymous_functions | bool | true/false | true | false |
allow_single_line_empty_anonymous_classes | bool | true/false | true | true |
anonymous_classes_opening_brace | string | 'next_line_unless_newline_at_signature_end'/'same_line' | 'same_line' | 'same_line' |
anonymous_functions_opening_brace | string | 'next_line_unless_newline_at_signature_end'/'same_line' | 'same_line' | 'same_line' |
classes_opening_brace | string | 'next_line_unless_newline_at_signature_end'/'same_line' | 'next_line_unless_newline_at_signature_end' | 同默认值 |
control_structures_opening_brace | string | 'next_line_unless_newline_at_signature_end'/'same_line' | 'same_line' | 'same_line' |
functions_opening_brace | string | 'next_line_unless_newline_at_signature_end'/'same_line' | 'next_line_unless_newline_at_signature_end' | 同默认值 |
两个位置枚举值的语义
'same_line':开括号放在签名(或控制结构条件)所在行的末尾,即 Allman 之外的 K&R / PSR-12 风格。'next_line_unless_newline_at_signature_end':默认把开括号放到下一行;但如果签名末尾本身已经存在换行(例如参数列表多行书写,且最后一个)与签名内容之间有换行),则保持{跟在)之后。测试用例'next line with newline before closing parenthesis'与'next line with newline in signature but not before closing parenthesis'分别演示了“)前有换行 → 同行”与“签名内有换行但)前无换行 → 下一行”两种判定结果。
future-mode 默认值差异
allow_single_line_anonymous_functions的默认值标注为“future-mode:false”。源码中使用Future::getV4OrV3(false, true)实现(见 BracesPositionFixer.php):在PHP_CS_FIXER_FUTURE_MODE=1环境变量下运行(或代码通过Future::runWithEnforcedFutureMode()开启时),该选项默认取false,与即将到来的 v4 默认行为对齐;常规运行则取true。Future类的实现在 src/Future.php,它同时负责在 future-mode 下把弃用用法升级为硬性报错。
默认配置下的修复效果
官方文档 Example #1 展示了默认配置的完整效果(diff 中-为原始代码、+为修复后):
<?php -class Foo { +class Foo +{ } -function foo() { +function foo() +{ } -$foo = function() -{ +$foo = function() { }; -if (foo()) -{ +if (foo()) { bar(); } -$foo = new class -{ +$foo = new class { };从中可以直观总结默认行为:具名类与方法/函数的开括号换行(classes_opening_brace、functions_opening_brace默认next_line_unless_newline_at_signature_end),而控制结构(if)、匿名函数与匿名类的开括号保持同行(对应三个选项默认'same_line')。这与 PSR-12 的风格基调一致。
分结构定制:7 个选项的独立示例
由于 7 个选项彼此独立,你完全可以只调整某一类结构的风格。官方文档给出了 8 个示例,下面逐一呈现。
示例 #2:控制结构开括号换行
配置['control_structures_opening_brace' => 'next_line_unless_newline_at_signature_end']:
<?php -if (foo()) { +if (foo()) +{ bar(); }该选项同样作用于else、elseif、for、foreach、while、do while、switch、try/catch/finally,测试类中'else (next line)'、'try catch finally (open brace on the next line)'等用例逐一验证(见 CurlyBracesPositionFixerTest.php)。
示例 #3:函数开括号同行
配置['functions_opening_brace' => 'same_line']:
<?php -function foo() -{ +function foo() { }示例 #4:匿名函数开括号换行
配置['anonymous_functions_opening_brace' => 'next_line_unless_newline_at_signature_end']:
<?php -$foo = function () { +$foo = function () +{ };示例 #5:类开括号同行
配置['classes_opening_brace' => 'same_line']:
<?php -class Foo -{ +class Foo { }示例 #6:匿名类开括号换行
配置['anonymous_classes_opening_brace' => 'next_line_unless_newline_at_signature_end']:
<?php -$foo = new class { +$foo = new class +{ };示例 #7:空匿名类允许单行
配置['allow_single_line_empty_anonymous_classes' => true](默认即true):
<?php $foo = new class { }; -$bar = new class { private $baz; }; +$bar = new class { +private $baz; +};可见:空匿名类({ }间没有任何代码)可以保持单行;而只要体内含有非空白、非注释内容(private $baz;),就会被强制展开为多行。从实现看,fixer 会扫描开闭括号之间的 Token,若存在非空白/非注释内容或换行,则$addNewlinesInsideBraces置为真,从而注入换行(BracesPositionFixer.php)。
示例 #8:匿名函数允许单行(默认)
配置['allow_single_line_anonymous_functions' => true](默认即true;future-mode 下为false):
<?php $foo = function () { return true; }; -$bar = function () { $result = true; - return $result; }; +$bar = function () { +$result = true; + return $result; +};单行闭包function () { return true; };保持不变;而原本挤在一行内、实际上含换行的闭包会被规范地展开。
多行签名与返回类型的智能处理
从测试用例可以确认,next_line_unless_newline_at_signature_end在处理带返回类型、联合类型、交叉类型甚至 DNF 类型的多行签名时相当细致:
- 多行参数 + 返回类型(
): int、): ?int、): array、): \Foo\Bar):当)前存在换行时,{跟随在)后同行(用例'next line with newline before closing parenthesis and return type');当)前无换行时,{换行。 - PHP 8.0 联合返回类型(
int|float)、8.1 交叉返回类型(Foo&Bar)、8.2 DNF 返回类型((Foo&Bar)|int|null)均有专门用例(provideFix80Cases、provideFix81Cases、provideFix82Cases数据提供器)。
这些用例同时证明了该规则会随着 PHP 版本语法演进持续维护,并在 CurlyBracesPositionFixerTest.php 中按@RequiresPhp注解分版本约束执行。
在官方规则集中的实际用法
当前仓库的官方规则集(PSR-12、PSR-2、Symfony 等)已经直接采用后继者braces_position规则名。以 PSR12Set 为例:
'braces_position' => [ 'allow_single_line_anonymous_functions' => false, 'allow_single_line_empty_anonymous_classes' => true, ],@PSR12明确要求匿名函数不允许单行(对应 PSR-12 关于闭包必须多行书写的规范),同时允许空匿名类单行。@PSR2与@Symfony也各自声明了该规则(SymfonySet)。因此,如果你通过@PSR12或@Symfony规则集启用风格检查,花括号位置已经由这些默认配置覆盖;若需自定义,只需在项目配置中覆盖braces_position的对应选项即可。规则的完整规则集归属与配置可参考 braces_position.rst 文档的 "Rule sets" 一节。
实战:命令行与项目配置
通过 CLI 直接启用
在项目根目录(存在.php-cs-fixer.dist.php或.php-cs-fixer.php配置文件时)运行:
php php-cs-fixer.phar fix未配置时默认套用@PSR12。若只想针对当前规则做一次检查而不改动文件,可使用--dry-run(等价于check命令):
php php-cs-fixer.phar check . --rules=braces_position php php-cs-fixer.phar fix . --rules=braces_position --dry-run用--rules以 JSON 形式传入完整配置:
php php-cs-fixer.phar fix . --rules='{"braces_position": {"classes_opening_brace": "same_line", "functions_opening_brace": "same_line", "allow_single_line_anonymous_functions": false}}'在项目配置文件中声明
<?php // .php-cs-fixer.dist.php return (new PhpCsFixer\Config()) ->setRules([ '@PSR12' => true, 'braces_position' => [ 'classes_opening_brace' => 'same_line', 'functions_opening_brace' => 'same_line', 'control_structures_opening_brace' => 'same_line', 'anonymous_functions_opening_brace' => 'same_line', 'anonymous_classes_opening_brace' => 'same_line', 'allow_single_line_anonymous_functions' => false, 'allow_single_line_empty_anonymous_classes' => true, ], ]) ;上述配置会把五类结构的开括号全部统一为同行风格(Allman → K&R 的一次性迁移场景)。命令行的--rules参数与配置文件规则覆盖了fix、check、list-files等命令的通用用法,详见 doc/usage.rst。
用 describe 快速查看规则说明
php php-cs-fixer.phar describe braces_position php php-cs-fixer.phar describe curly_braces_position # 会提示已弃用迁移清单与兼容性承诺
由于curly_braces_position将在 v4.0 移除,建议按如下清单迁移:
- 全局搜索配置文件中的
curly_braces_position键,替换为braces_position; - 配置数组内容保持不变(7 个选项名与取值完全一致);
- 关注
allow_single_line_anonymous_functions的默认值变更:v3 默认true,v4 默认改为false(可用PHP_CS_FIXER_FUTURE_MODE=1 php php-cs-fixer.phar check .提前验证 future 行为,参考 doc/usage.rst); - 运行
check确认无新增违规,再执行fix落地。
关于行为的权威性:仓库文档明确声明,测试类定义了官方支持的行为,每个测试用例都是向后兼容承诺(backward compatibility promise)的一部分。因此 CurlyBracesPositionFixerTest.php 中从'if (default)'、'else (next line)'到'open brace not preceded by space and followed by a space and comment'的全部用例,就是你判断某段代码会被如何修复的最可靠依据;BracesPositionFixerTest 则对应新规则名的同等行为契约。
小结
curly_braces_position(及其后继者braces_position)是 PHP-CS-Fixer 中对花括号布局最精细的一把“手术刀”:7 个独立配置项把类、函数、匿名函数、匿名类与控制结构五类开括号位置解耦,配合allow_single_line_*两个布尔开关处理单行例外,再叠加对多行签名、返回类型与注释边界的细节处理,足以应对团队代码风格从 Allman 到 K&R(或反之)的整体迁移,也能只针对某一类结构做微调。理解其代理 Fixer 架构、next_line_unless_newline_at_signature_end的换行判定规则以及与SingleLineEmptyBodyFixer、StatementIndentationFixer等规则的执行顺序,能帮助你在配置复杂规则集时做出正确取舍。
- 开发工具
- 代码质量
- 静态分析
- Lint
- 格式化
【免费下载链接】PHP-CS-Fixer
A tool to automatically fix PHP Coding Standards issues
相关推荐
三步搞定B站热门活动购票难题:biliTickerBuy开源工具完全指南
三步搞定B站热门活动购票难题:biliTickerBuy开源工具完全指南 还在为抢不到Bilibili热门活动门票而烦恼吗?biliTickerBuy是一款专为
开发工具代码质量静态分析Lint格式化如何3分钟完成黑苹果配置:OpCore Simplify图形化工具终极指南
如何3分钟完成黑苹果配置:OpCore Simplify图形化工具终极指南 还在为复杂的黑苹果OpenCore配置而头疼吗?OpCore Simplify是一款
开发工具代码质量静态分析Lint格式化PHP-CS-Fixer 规则集 @PER-CS2.0 完整指南:规则构成、配置详解与迁移到 @PER-CS2x0
PHP CS Fixer 规则集 @PER CS2.0 完整指南:规则构成、配置详解与迁移到 @PER CS2x0 导读 @PER CS2.0 是 PHP CS
开发工具代码质量静态分析Lint格式化
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考