Rector 自动化重构规则编写
解读
在国内一线互联网公司的 PHP 技术面试中,Rector 已从“加分项”变成“必会项”。面试官抛出“写一条 Rector 规则”并不是让你背诵文档,而是考察三件事:
- 是否真在项目中用 Rector 解决过“代码腐化”问题;
- 是否理解 AST(抽象语法树)的节点类型与遍历机制;
- 能否把业务约束(如公司编码规范、安全红线)翻译成可自动执行的规则。
因此,答题时必须给出“可落地、可复现、可集成到 CI”的完整代码,并解释规则背后的设计思路。规则难度要适中:太简单(如把 array() 改成 [])会被认为“没深度”;太复杂(如跨文件静态分析)在 10 分钟面试里讲不清。下面以“强制把 date('Y-m-d') 换成 Carbon::now()->toDateString()”为例,展示国内面试官最想听到的答题结构。
知识点
- Rector 运行流程:FileProcessor → Parser → NodeTraverser → Rule → Printer。
- 核心接口:
Rector\Core\Contract\Rector\RectorInterface,实际继承AbstractRector即可。 - 节点类型:PhpParser\Node\Expr\FuncCall 代表函数调用,Node\Name 区分函数名。
- 节点替换:return 新节点即完成替换,Rector 会自动写回文件。
- 规则测试:Rector 自带
AbstractRectorTestCase,放在 tests/Rector 目录,GitHub CI 可直接跑。 - 国内落地要点:
- 规则必须加
@see指向公司 Wiki 地址,方便审计; - 规则 class 名以
*Rector结尾,符合 PSR-4; - 禁止直接 new 类,用
NodeFactory创建,避免语法版本兼容问题; - 上线前要在
rector.php里配置skip,防止老模块爆炸。
- 规则必须加
答案
<?php
declare(strict_types=1);
namespace App\Rector;
use PhpParser\Node;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Name;
use PhpParser\Node\Name\FullyQualified;
use Rector\Core\Rector\AbstractRector;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
/**
* 强制把 date('Y-m-d') 替换为 Carbon::now()->toDateString()
* 符合公司《PHP 安全开发规范 V5.2》第 3.4 条:时间操作统一用 Carbon。
*
* @see https://wiki.company.com/php-standards#time
*/
final class DateToCarbonRector extends AbstractRector
{
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition(
'Replace date("Y-m-d") with Carbon::now()->toDateString()',
[
new CodeSample(
<<<'CODE_SAMPLE'
$date = date('Y-m-d');
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
$date = \Carbon\Carbon::now()->toDateString();
CODE_SAMPLE
),
]
);
}
/**
* @return array<class-string<Node>>
*/
public function getNodeTypes(): array
{
return [FuncCall::class];
}
/**
* @param FuncCall $node
*/
public function refactor(Node $node): ?Node
{
if (! $this->isName($node->name, 'date')) {
return null;
}
// 只处理单参数且为 'Y-m-d' 的情况
if (count($node->args) !== 1) {
return null;
}
$firstArg = $node->args[0];
if (! $firstArg->value instanceof Node\Scalar\String_) {
return null;
}
if ($firstArg->value->value !== 'Y-m-d') {
return null;
}
// 构建 \Carbon\Carbon::now()->toDateString()
$staticCall = new StaticCall(
new FullyQualified('Carbon\Carbon'),
'now'
);
$methodCall = new Node\Expr\MethodCall($staticCall, 'toDateString');
return $methodCall;
}
}
配套测试 tests/Rector/DateToCarbonRector/DateToCarbonRectorTest.php:
<?php
declare(strict_types=1);
namespace Tests\Rector\DateToCarbonRector;
use App\Rector\DateToCarbonRector;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
final class DateToCarbonRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideData()
*/
public function test(string $file): void
{
$this->doTestFile($file);
}
public function provideData(): \Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
}
public function provideConfigFilePath(): string
{
return __DIR__ . '/config.php';
}
}
Fixture 文件 Fixture/simple.php.inc:
<?php
namespace RectorPrefix2025;
class SomeController
{
public function run()
{
$a = date('Y-m-d');
}
}
?>
-----
<?php
namespace RectorPrefix2025;
class SomeController
{
public function run()
{
$a = \Carbon\Carbon::now()->toDateString();
}
}
?>
CI 集成(.github/workflows/rector.yml 片段):
- name: Run Rector
run: |
vendor/bin/rector process --dry-run --ansi
至此,一条可合并到主干、可自动回归测试的 Rector 规则就写完了。面试时把这段代码粘到 IDE,现场跑 vendor/bin/rector process demo.php --dry-run,能看到 diff 红绿对比,说服力瞬间拉满。
拓展思考
-
规则灰度:如何在 monorepo 里只对指定微服务生效?
答:在rector.php里用Skipper配置路径正则,如'/^packages\/(?!order)/',实现“订单服务先行,其余观望”。 -
性能调优:国内某头部电商 200 万行 PHP 遗产代码,一次 Rector 全量扫描要 45 分钟,如何把耗时降到 5 分钟?
答:- 开启
opcache.enable_cli=1; - 用
rector-cache把序列化的 AST 缓存到 Redis; - 按 Git diff 只跑变更文件,CI 里用
vendor/bin/rector process $(git diff --name-only origin/main...HEAD)。
- 开启
-
安全规则:如何写一条“强制把
$_GET直接取值”替换成filter_input(INPUT_GET, ...)的规则?
提示:监听Node\Expr\ArrayDimFetch,判断var是否为Node\Expr\Variable且 name 等于_GET,再构造filter_input函数调用即可。该规则可直接堵住大量 XSS 入口,是安全团队最爱的扫描项。 -
与 PHPStan、EasyCodingStandard 的协同:
国内很多团队把 Rector、PHPStan、ECS 串成“代码质量三叉戟”:- Rector 负责“改”,PHPStan 负责“检”,ECS 负责“格”;
- 在 pre-push 钩子中先
rector process --dry-run,再phpstan analyse,最后ecs check,三道闸门全部通过才允许推送。
掌握以上思路,候选人就能把“写一条 Rector 规则”这个小问题,讲成“百万级代码库自动化治理”的大故事,稳稳拿到 PHP 高工 offer。