pmjones / php-styler
PHP-Parser的配套工具,可从抽象语法树(AST)重建PHP代码。
Requires
- php: ^8.1 | ^8.2 | ^8.3
- nikic/php-parser: ^4.19
- pmjones/auto-shell: ^1.0
Requires (Dev)
- pds/composer-script-names: ^1.0
- pds/skeleton: ^1.0
- phpstan/phpstan: ^1.10
- phpunit/phpunit: ^10.0
README
警告!!!
PHP-Styler将完全重新格式化您的PHP代码,丢弃任何之前的格式化。
麦考伊:如果这个工具在已经存在[格式化]的地方使用怎么办?
斯波克:它会为了它的新矩阵而破坏这种[格式化]。
麦考伊:它的新矩阵?你有什么想法吗?
斯波克:我并不是试图评估它的[美学]影响。
-- 《星际迷航2:怒火中烧》(转述)
您可以在http://php-styler.com/尝试PHP-Styler的在线演示。
简介
PHP-Styler是PHP-Parser的配套工具,用于重构已分解为抽象语法树(AST)的PHP代码。
与PHP-Parser的格式化打印器不同,PHP-Styler的主要设计目标是输出自定义。有关更多信息,请参阅README-CUSTOM.md。
PHP-Styler针对声明/定义文件(类、接口、枚举、特质)和脚本文件。
PHP-Styler不适用于基于PHP的模板,因为它不使用替代控制结构。也许未来的版本将包括一个用于基于PHP模板的自定义AlternativeStyler。
工作原理
PHP-Styler使用多遍系统来重新格式化和美化PHP代码
- 解析器将代码转换为节点元素的抽象语法树,并在过程中应用访问者的转换。
- 打印器将节点树展平为一系列可打印元素。
- 美化器使用一系列行对象将每个可打印元素转换回文本;它在过程中应用水平间距、垂直间距和行分割规则。
注意
解析器还将在预处理步骤中将所有使用(关键字之间有空格的)
else if
转换为(没有空格的)elseif
;这既是一个实用问题,也是一个风格问题。参见图#4和nikic/PHP-Parser#948。
设计目标
-
逻辑保持。重构的PHP代码将继续按预期运行。
-
水平和垂直间距。自动缩进和空白行放置。
-
行长度控制。当单行过长时,自动拆分为多行。
-
Diff友好。默认输出应有助于diff中的噪声减少。
-
自定义。通过扩展美化器并覆盖每个想要更改的可打印元素的相应方法来更改可打印元素的输出样式。
-
注释保持。PHP-Parser允许的最大程度。
美化示例
有关几乎详尽的样式示例,请参阅示例目录,或尝试在您的源文件上使用安全的preview
命令。
类似产品
PHP CS Fixer 在PHP代码格式化领域是领先者。它提供了大量自定义选项来修复(或保留)PHP代码的特定元素。然而,它极其复杂,修改起来可能比较困难。
我所知道的最早的PHP代码修复工具是 PHP_Beautifier。其他较新的修复工具包括 PHP_CodeSniffer/PHPCBF 和 ECS。
Python的格式化工具 Black 似乎与PHP-Styler有着类似的设计目标和操作。
同样,dart_style 是Dart语言的格式化工具。(更多关于它的工作原理,请参阅 这里。)
最后,有一个 PHP插件用于Prettier,它使用JavaScript按照自己的规则替换所有PHP代码的格式。
使用方法
安装
使用 composer
将PHP-Styler添加为开发依赖项
composer require --dev pmjones/php-styler 0.x@dev
将默认的 php-styler.php
配置文件复制到您的包根目录
cp ./vendor/pmjones/php-styler/resources/php-styler.php .
预览格式化
安全地预览PHP-Styler如何重新构建源PHP文件
./vendor/bin/php-styler preview ./src/My/Source/File.php
通过传递 -c
或 --config
来指定一个替代的配置文件
./vendor/bin/php-styler preview \
-c /path/to/other/php-styler.php \
./src/My/Source/File.php
通过传递 --debug-parser
将PHP-Parser AST Node 对象输出到预览中,以及/或通过传递 --debug-printer
将PHP-Styler的 Printable 对象数组输出到预览中。
应用格式化
将PHP-Styler应用于配置文件中标识的所有文件,并用新的格式覆盖它们
./vendor/bin/php-styler apply
通过传递 -c
或 --config
来指定一个替代的配置文件
./vendor/bin/php-styler apply -c /path/to/other/php-styler.php
PHP-Styler只会对修改时间 晚于 缓存文件的文件应用格式。要强制对所有文件进行格式化,无论修改时间如何,请传递 --force
选项
./vendor/bin/php-styler apply --force
在 apply
之后更改配置文件将使缓存失效,这意味着 --force
,从而使得PHP-Styler将格式应用于所有文件。
要明确将样式应用于配置文件中指定的路径以外的路径,请传递一个空格分隔的文件和目录列表作为参数
./vendor/bin/php-styler apply ./src/File.php ./resources/
在明确指定路径时,不会考虑缓存时间,就像已经传递了 --force
选项一样。
检查格式化
检查配置文件中标识的所有文件,以查看它们是否需要格式化,而不更改任何文件
./vendor/bin/php-styler check
通过传递 -c
或 --config
来指定一个替代的配置文件
./vendor/bin/php-styler apply -c /path/to/other/php-styler.php
如果所有文件看起来都正常,则返回码为 0
。如果有一个或多个文件看起来需要格式化,则返回码为 1
。
配置
默认的 php-styler.php
配置文件看起来像这样
<?php use PhpStyler\Config; use PhpStyler\Files; use PhpStyler\Styler; return new Config( files: new Files(__DIR__ . '/src'), styler: new Styler(), cache: __DIR__ . '/.php-styler.cache', );
-
iterable $files
是任何iterable
的文件名列表,这些文件应应用PHP-Styler。 (如果PHP-Styler的 Files 对象不足以满足您的需求,请尝试使用 Symfony Finder。) -
Styler $styler
是任何 Styler 实例,无论是默认实例还是任何自定义扩展类。 -
?string $cache
是缓存文件名;此文件的最后修改时间表示PHP-Styler最后一次应用的时间。如果$cache
为 null,则不使用缓存。
可以通过以下构造函数参数配置 Styler 实例
-
string $eol = "\n"
:要使用的行结束字符串。 -
int $lineLen = 88
:在PHP-Styler尝试自动拆分行之前,最大行长度。 -
int $indentLen = 4
:缩进长度(空格)。 -
bool $indentTab = false
:当true
时,使用制表符("\t"
)进行缩进而不是空格;当计算行长度时,使用$indentLen
作为制表符宽度。
这里是一个为Windows换行符配置的120字符行宽,8个空格缩进的样式器
<?php use PhpStyler\Config; use PhpStyler\Files; use PhpStyler\Styler; return new Config( files: new Files(__DIR__ . '\\src'), styler: new Styler( eol: "\r\n", lineLen: 120, indentLen: 8, indentTab: true, ), );
避免归责
第一次将PHP-Styler应用到源文件可能会引入大量更改,这将使得通过git blame
追踪作者变得困难。
您可以通过在您的仓库中添加一个.git-blame-ignore-revs
文件,并将初始重新格式化提交的完整哈希值添加到其中,来告诉Git忽略这个初始的重新格式化过程。
- 对您的代码库运行
php-styler apply
并提交更改。 - 运行
git log
并从该提交中复制完整的40字符哈希字符串。 - 创建并提交一个名为
.git-blame-ignore-revs
的文件,将哈希值粘贴到其中,可能还会添加注释。 - 配置Git以查看该文件:
git config blame.ignoreRevsFile .git-blame-ignore-revs
哇:现在git blame
将忽略该文件在查看作者历史时的内容,GitHub的blame
用户界面也会如此。
(另请参阅https://git.js.cn/docs/git-blame#Documentation/git-blame.txt---ignore-revs-fileltfilegt。)
行分割
自动
最初,PHP-Styler将每个语句/指令作为一个单独的行来构建。如果该行“太长”(默认为88个字符),则样式器会尝试将代码拆分到多行中。它是通过应用一个或多个规则来实现的
implements
在逗号处拆分。- 箭头函数在
=>
处拆分。 - 字符串连接在点处拆分。
- 条件在括号处拆分。
- 表示优先级的括号被拆分。
- 三元运算符在
?
、:
和?:
处拆分。 - 布尔
||
和逻辑or
运算符被拆分。 - 布尔
&&
和逻辑and
运算符被拆分。 - 数组元素在逗号处拆分。
- 参数列表在逗号处拆分。
- Coalesce
??
运算符被拆分。 - 成员运算符在
::
、::$
、->
和?->
处拆分。 - 参数列表在逗号处拆分。
如果第一条规则没有使行足够短,则应用第二条规则,然后是第三条,依此类推。
最后,PHP-Styler将为自动拆分的每一行添加一个空白边框。
行拆分逻辑试图尽可能符合习惯;也就是说,PHP-Styler试图考虑常见的行拆分习惯,而不是对元素进行权衡计算。参考项目包括
- cakephp/database
- laminas/laminas-mvc
- nette/application
- qiq/qiq
- sapien/sapien
- slim/slim
- symfony/http-foundation
注释
有时您可能希望强制将行拆分得更加广泛。例如,具有许多元素的深度嵌套数组,当每个元素都在其自己的行上时,看起来可能更好,不管这些元素可能有多短。
要强制行的拆分广泛,请在有问题的行上方添加注释@php-styler-expansive
。例如,这个数组...
$foo = ['bar', 'baz', 'dib'];
...通常会显示在一行中。然而,当添加@php-styler-expansive
注释...
/** @php-styler-expansive */ $foo = [ 'bar', 'baz', 'dib', ];
...元素将被拆分得更加广泛地跨越多行。
PHP-Styler识别单行注释/** @php-styler-expansive */
和// @php-styler-expansive
,以及典型的docblock注释
/** * @php-styler-expansive */
修复混乱的输出
如果PHP-Styler生成“丑陋”的、“奇怪的”或“混乱”的结果,可能表明PHP-Styler的工作方式有问题;请提交问题。
或者,这可能表明源行应该重构。以下是一些建议
-
增加最大行长度。默认长度为88个字符(比通常建议的80个字符长度多10%,以留出一些调整空间)。然而,一些代码库倾向于使用更长的行,因此增加行长度可能会导致更令人愉悦的行分割。
-
从参数和参数列表中移除注释。
-
将行首或行尾的行内注释移动到行上方。
-
将单行拆分成多个较短的行。
-
将嵌在参数中的闭包分配给单独的变量。
-
将嵌在连接操作中的函数调用分配给单独的变量。
-
将嵌在单个语句中的多个三元运算符分配给单独的变量。
不幸的是,由于PHP-Parser处理带有插值变量的双引号字符串(“封装”字符串)的方式,换行符和一些其他空白字符(\f
、\r
、\t
、\v
)在字符串内部表现为字面量的\n
(等等)。例如,此代码...
$sql = " SELECT * FROM {$table} ";
...
$sql = "\n SELECT TABLE_NAME\n FROM {$table}\n";
...这并不是我所期望看到的。
直到PHP-Parser的工作方式发生变化,我能想到的唯一解决方案是使用here文档语法。然后此代码...
$sql = <<<SQL SELECT * FROM {$table} SQL;
...
注意事项
行长度
即使应用了所有行分割规则,一行仍可能变得“过长”。例如,如果一行包含一个非常长的引用字符串,PHP-Styler无法为您分割它。
重新排列代码
PHP-Styler不会
- 重新组合
use
导入 - 分割注释行
- 分割引用字符串、here文档或now文档
水平对齐
PHP-Styler将取消以下行的对齐...
$foo = 'longish' . $bar
$foo = 'short' . $bar;
$foo = 'muchlonger' . $bar;
...
$foo = 'longish' . $bar
$foo = 'short' . $bar;
$foo = 'muchlonger' . $bar;
垂直间距
PHP-Styler将压缩以下行...
$foo = 'longish' . $bar
$foo = 'short' . $bar;
$foo = 'muchlonger' . $bar;
...
$foo = 'longish' . $bar
$foo = 'short' . $bar;
$foo = 'muchlonger' . $bar;
如果您想添加额外的垂直间距,请添加注释;注释行在其上方获得一个空白行。
// baseline foo
$foo = 'longish' . $bar
// reassign foo
$foo = 'short' . $bar;
// reassign foo again
$foo = 'muchlonger' . $bar;
注释行
注释行始终“附加”在其后续行上,而不是其前一行。例如,看起来附加到其前一行上的// no break
注释...
case 'foo': $foo = 'bar'; // no break case 'baz': $baz = 'dib' break;
...
case 'foo': $foo = 'bar'; // no break case 'baz': $baz = 'dib' break;
这是PHP-Parser的一个限制;因为它目前不报告节点开始和结束的列号,PHP-Styler无法推断注释应该如何附加。
此外,某些注释可能在它们是某些结构中唯一元素时完全消失
switch ($foo) { /* this comment disappears */ } function bar(/* this comment disappears */) { } $fb = 'veryVeryLongStringToConcatenate' // this comment disappears . 'veryVeryLongStringToConcatenate' // this comment disappears . 'veryVeryLongStringToConcatenate' // this comment disappears . 'veryVeryLongStringToConcatenate' // this comment disappears . 'veryVeryLongStringToConcatenate';
这看起来像是PHP-Parser本身的问题;cf. nikic/PHP-Parser#950.
同样,一个最终数组元素上的最后一个行内注释可能会消失
$map = [ 34 => 'quot', // quotation mark 38 => 'amp', // ampersand 60 => 'lt', // less-than sign 62 => 'gt', // greater-than sign -- this comment disappears ];
这也看起来像是PHP-Parser本身的问题。