news 2026/9/23 6:55:01

PHP-CS-Fixer `curly_braces_position` 规则详解:花括号位置的可配置化统一与 `braces_position` 迁移指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
PHP-CS-Fixer `curly_braces_position` 规则详解:花括号位置的可配置化统一与 `braces_position` 迁移指南
  • 开发工具
  • 代码质量
  • 静态分析
  • Lint
  • 格式化

【免费下载链接】PHP-CS-Fixer

A tool to automatically fix PHP Coding Standards issues

项目地址:https://gitcode.com/gh_mirrors/ph/PHP-CS-Fixer
点击查看免费下载

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_functionsallow_single_line_empty_anonymous_classesanonymous_classes_opening_braceanonymous_functions_opening_braceclasses_opening_bracecontrol_structures_opening_bracefunctions_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_braceclasses_opening_brace两个配置。
  • 函数/方法/匿名函数:遇到T_FUNCTION后定位开括号;若TokensAnalyzer->isLambda()判定为闭包,则走anonymous_functions_opening_brace,否则走functions_opening_brace
  • 控制结构:通过CONTROL_STRUCTURE_TOKENS常量匹配T_DECLARET_DOT_ELSET_ELSEIFT_FINALLYT_FORT_FOREACHT_IFT_WHILET_TRYT_CATCHT_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 会执行两类核心操作:

  1. 保证{后的换行:当花括号体内应换行($addNewlinesInsideBraces为真)且开括号后没有换行、没有同行注释、后跟的也不是?>时,注入换行符 + 当前缩进
  2. 移动开括号本身:根据配置把{之前的空白替换成' '(同一行)或换行符 + 缩进(下一行),必要时连同相邻注释一起搬移;代码中专门处理了/* *///注释位于开括号前后的各种组合(对应测试用例中的'open brace preceded by comment and whitespace'等场景)。

一个值得注意的细节是:next_line_unless_newline_at_signature_end选项在搬移时还会回溯参数括号,如果签名本身已经是多行的(例如参数换行书写、返回类型跨行),则回退为单个空格而不是强制换行——这正是该选项名称中unless_newline_at_signature_end的语义,下面会结合示例说明。

执行优先级(Priority)

getPriority()返回-2,并声明了与其他规则的先后关系(见 BracesPositionFixer.php):

  • 必须运行在SingleLineEmptyBodyFixerStatementIndentationFixer之前
  • 必须运行在ControlStructureBracesFixerMultilinePromotedPropertiesFixerNoMultipleStatementsPerLineFixer之后

仓库中的集成测试文件 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.testcurly_braces_position,statement_indentation.testno_multiple_statements_per_line,curly_braces_position.test等组合用例,说明该规则与花括号补充、语句缩进、单行多语句等规则存在紧密的先后依赖。

配置项全解

该规则的所有配置均通过BracesPositionFixer::createConfigurationDefinition()(源码位置)声明,使用FixerOptionBuilder定义类型、允许值与默认值。下表汇总 7 个选项:

配置项类型允许值默认值默认值(future-mode)
allow_single_line_anonymous_functionsbooltrue/falsetruefalse
allow_single_line_empty_anonymous_classesbooltrue/falsetruetrue
anonymous_classes_opening_bracestring'next_line_unless_newline_at_signature_end'/'same_line''same_line''same_line'
anonymous_functions_opening_bracestring'next_line_unless_newline_at_signature_end'/'same_line''same_line''same_line'
classes_opening_bracestring'next_line_unless_newline_at_signature_end'/'same_line''next_line_unless_newline_at_signature_end'同默认值
control_structures_opening_bracestring'next_line_unless_newline_at_signature_end'/'same_line''same_line''same_line'
functions_opening_bracestring'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 默认行为对齐;常规运行则取trueFuture类的实现在 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_bracefunctions_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(); }

该选项同样作用于elseelseifforforeachwhiledo whileswitchtry/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)均有专门用例(provideFix80CasesprovideFix81CasesprovideFix82Cases数据提供器)。

这些用例同时证明了该规则会随着 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参数与配置文件规则覆盖了fixchecklist-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 移除,建议按如下清单迁移:

  1. 全局搜索配置文件中的curly_braces_position键,替换为braces_position
  2. 配置数组内容保持不变(7 个选项名与取值完全一致);
  3. 关注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);
  4. 运行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的换行判定规则以及与SingleLineEmptyBodyFixerStatementIndentationFixer等规则的执行顺序,能帮助你在配置复杂规则集时做出正确取舍。

  • 开发工具
  • 代码质量
  • 静态分析
  • Lint
  • 格式化

【免费下载链接】PHP-CS-Fixer

A tool to automatically fix PHP Coding Standards issues

项目地址:https://gitcode.com/gh_mirrors/ph/PHP-CS-Fixer
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/23 6:55:02

3个核心步骤搞懂服务器日志原理新手避坑指南

3个核心步骤搞懂服务器日志原理新手避坑指南 配置环境就卡半天,是不是觉得服务器日志像一团乱麻?很多新手一上来就盯着 tail -f 看,结果发现日志刷屏根本看不清重点,或者生产环境日志丢了都不知道去哪找。别急,今天咱们不背命令,直接扒开服务器日志的底层逻辑,用工程师的视角把这事讲透。 新手避坑…

作者头像 李华
网站建设 2026/9/23 6:54:50

3个步骤搞定2026最新mp3歌曲免费下载网接口原理

3个步骤搞定2026最新mp3歌曲免费下载网接口原理 面试被问原理答不上来?别慌,这往往是新手最容易踩的坑。很多开发者在面对 mp3歌曲免费下载网 这类资源聚合站的逆向工程时,往往只盯着页面看,忽略了底层数据流。…

作者头像 李华
网站建设 2026/9/23 6:53:31

论文降AI率攻略:研究生论文AI率95%,一次降达标线完整记录

论文降AI率攻略&#xff1a;研究生论文AI率95%&#xff0c;一次降达标线完整记录 知网AIGC检测95%——这是我见过的最高AI率之一。 这是我朋友的硕士论文&#xff0c;研究方向是计算机视觉&#xff0c;有一半的内容用DeepSeek生成后直接粘贴&#xff0c;几乎没做过修改。降AI…

作者头像 李华
网站建设 2026/9/23 6:53:17

百度百发3个核心考点解析:新手避坑指南,面试不挂

百度百发3个核心考点解析:新手避坑指南,面试不挂 官方文档堆砌术语,读完还是云里雾里?很多新手在准备【百度百发】相关技术栈面试时,最大的痛点就是 抓不住重点 。你翻遍了官方…

作者头像 李华
网站建设 2026/9/23 6:53:13

苹果c入门完整示例:解决复制代码报错难题

苹果c入门完整示例:解决复制代码报错难题 刚把网上扒来的苹果c代码拷进Xcode,点运行直接报一堆错,心里是不是特别慌?别急,这种“复制即翻车”的情况太常见了,核心往往不是代码逻辑本身,而是环境配置、依赖版本或命名空间没对齐。很多新手卡在这一步,其实只要搞懂底层逻辑,配好一套能跑的完整示例,问题就解…

作者头像 李华
网站建设 2026/9/23 6:52:47

P920实战项目避坑指南:3个核心差异决定成败

P920实战项目避坑指南:3个核心差异决定成败 官方文档翻了三遍还是看不懂 P920 的核心逻辑?别急,这怪你也不怪文档。很多工程师在做 实战项目 时,最头疼的就是 P920…

作者头像 李华