你的项目还跑在 PHP 7.4 上,想升到 8.x 但面对几百个文件的语法差异无从下手?团队里新人写的代码风格参差不齐,code review 累得半死?Rector 就是用来解决这些问题的。它能自动把旧版 PHP 代码升级到新版本,也能按规则批量重构代码质量,而且整个过程可以先用 dry-run 预览,确认无误再真正改动。

安装
composer require rector/rector --dev
安装完成后在项目根目录创建 rector.php,所有规则都在这里配置。没有 init 命令,手动建就行:
<?php
use Rector\Config\RectorConfig;
use Rector\Set\ValueObject\SetList;
return RectorConfig::configure()
->withPaths([__DIR__ . '/src', __DIR__ . '/tests'])
->withSets([
SetList::PHP_80,
]);
先预览不改文件:
vendor/bin/rector src --dry-run
确认没问题后去掉 --dry-run 正式执行:
vendor/bin/rector src
实际跑起来是这个效果,diff 清楚地告诉你每处会怎么改:

一条命令升级 PHP 版本
这是 Rector 最核心的能力。内置了从 PHP 5.3 到 8.6 全系列版本的升级规则集,一条命令完成过去需要数周的手工迁移。
先看效果。这是一段典型的 PHP 7.x 风格代码:
// 升级前
class UserService
{
private $repository;
private $cache;
public function __construct($repo, $cache)
{
$this->repository = $repo;
$this->cache = $cache;
}
public function findUser($id)
{
$key = 'user_' . $id;
$data = $this->cache->get($key);
if ($data === false) {
$data = $this->repository->find($id);
$this->cache->set($key, $data, 3600);
}
return $data;
}
}
用 SetList::PHP_80 跑一次之后:
// 升级后
class UserService
{
public function __construct(
private $repository,
private $cache // 构造器属性提升(PHP 8.0 特性)
) {
}
public function findUser(string $id) // 参数加了类型声明
{
$key = 'user_' . $id;
$data = $this->cache->get($key);
if ($data === false) {
$data = $this->repository->find($id);
$this->cache->set($key, $data, 3600);
}
return $data;
}
}
构造函数里的赋值模式被自动识别并改成了 构造器属性提升(constructor promotion),这是 PHP 8.0 的语法糖——属性声明、参数声明、赋值三步合成一步。
跨版本升级也只需要链式添加规则集:
return RectorConfig::configure()
->withPaths([__DIR__ . '/src'])
->withSets([
SetList::PHP_80, // 命名参数、match 表达式、str_contains 等
SetList::PHP_81, // 枚举、readonly 属性、never 返回类型
SetList::PHP_82, // true 类型、动态属性弃用、随机数扩展
SetList::PHP_83, // 类常量类型化、json_validate() 等
SetList::PHP_84, // 属性钩子、不带括号的新实例化等
]);
每个规则集对应一个 PHP 版本的语法变更和废弃特性替换。框架也能一起升——Symfony、Laravel、PHPUnit、Doctrine 都有对应的社区规则包,官方维护的是 Symfony、PHPUnit 和 Doctrine 三个。
批量重构代码质量
不升级 PHP 版本也没关系,Rector 同样能帮你清理代码。而且多条规则可以同时生效,一次运行解决一堆问题。
来看一个真实案例。下面这段代码集齐了常见的老项目味道:无类型声明、嵌套过深的 if-else、未使用的私有方法、strpos 旧写法:
// 重构前
class UserService
{
private $repository;
private $cache;
public function __construct($repo, $cache)
{
$this->repository = $repo;
$this->cache = $cache;
}
public function isActive($user)
{
if ($user !== null) {
if ($user->status === 'active') {
if (strtotime($user->lastLoginAt) > time() - 86400 * 30) {
return true;
}
}
}
return false;
}
private function formatName($user)
{
return $user->firstName . ' ' . $user->lastName;
}
private function unusedHelper()
{
return 'nobody calls me';
}
}
function check_string_exists($haystack, $needle)
{
if (strpos($haystack, $needle) !== false) {
return true;
}
return false;
}
一次性启用代码质量相关的四套规则:
return RectorConfig::configure()
->withPaths([__DIR__ . '/src'])
->withSets([
SetList::CODE_QUALITY, // 通用质量改进
SetList::DEAD_CODE, // 死代码清除
SetList::EARLY_RETURN, // 提前返回
SetList::TYPE_DECLARATION, // 类型声明补全
]);
跑完之后,同一个文件变成了这样:
// 重构后(8 条规则同时生效)
class UserService
{
public function __construct( // [1] 构造器属性提升
private $repository,
private $cache
) {}
public function isActive($user): bool // [2] 返回类型声明
{ // [3] 嵌套 if → early return
if ($user === null) {
return false;
}
if ($user->status !== 'active') {
return false;
}
return strtotime((string) $user->lastLoginAt) > time() - 86400 * 30;
}
// [4] formatName() 和 unusedHelper() 被删除了 —— 未使用的私有方法
}
function check_string_exists($haystack, $needle): bool // [5][6] 参数+返回类型
{ // [7] strpos → str_contains
return str_contains((string) $haystack, (string) $needle); // [8] 三元简化
}
一个文件,一次运行,8 条规则同时生效。构造器属性提升、类型声明、early return、死代码清除、str_contains 替换、布尔返回值简化——全部自动完成。
拆开看每条规则单独做了什么:
死代码清除
未使用的私有方法是老项目的顽疾。没人敢删,因为不确定哪里在用反射调用。但如果确定没用,DEAD_CODE 规则集会直接帮你清掉:
// 前
class ReportGenerator
{
public function build()
{
$data = $this->fetchData();
return $this->format($data);
}
private function fetchData() { return []; }
private function format($data) { return json_encode($data); }
private function deprecatedMethod()
{
// 旧代码遗留
return null;
}
}
// 后 —— deprecatedMethod() 被删除,因为它从未被调用
class ReportGenerator
{
public function build()
{
$data = $this->fetchData();
return $this->format($data);
}
private function fetchData() { return []; }
private function format($data) { return json_encode($data); }
}
建议配合 git diff 逐处审查删除内容,毕竟反射或动态调用的场景 Rector 无法感知。
Early Return
嵌套三层以上的 if-else 是可读性灾难:
// 前
public function process(User $user): ?string
{
if ($user->isActive()) {
if ($user->hasPermission('write')) {
return $user->getName();
}
}
return null;
}
// 后
public function process(User $user): ?string
{
if (!$user->isActive()) {
return null; // 先排除不符合的
}
if (!$user->hasPermission('write')) {
return null; // 再排除下一个
}
return $user->getName(); // 剩下的就是正常路径
}
注意:如果同时启用了 CODE_QUALITY,里面的 CombineIfRector 可能会把同样的嵌套 if 合并成 && 条件而不是 early return。两个规则存在竞争关系,根据团队偏好选其中一个就行。
类名/命名空间全项目重命名
要做类名迁移的时候,这条规则能省下几天的工作量:
// 前 —— 引用了旧的命名空间
use App\Legacy\Utils\Helper;
use App\Legacy\Models\Customer;
class OrderController
{
public function show($id)
{
$helper = new Helper(); // 旧类名
$customer = new Customer($id); // 旧类名
return $helper->format($customer->getName());
}
}
// 配置
->withConfiguredRule(RenameClassRector::class, [
'App\Legacy\Utils\Helper' => 'App\Support\Helper',
'App\Legacy\Models\Customer' => 'App\Models\User',
]);
// 后 —— use 语句和实例化全部替换
class OrderController
{
public function show($id)
{
$helper = new \App\Support\Helper(); // 新命名空间
$customer = new \App\Models\User($id); // 新类名
return $helper->format($customer->getName());
}
}
基于 AST 的替换,不是文本查找——不会误伤字符串或注释里出现的同名内容。
精确控制:跳过不想改的
不是所有自动修改都是你想要的。跳过特定文件、目录或者某条规则:
return RectorConfig::configure()
->withPaths([__DIR__ . '/src'])
->withSets([SetList::CODE_QUALITY])
->withSkip([
__DIR__ . '/src/Legacy/', // 跳过整个目录
__DIR__ . '/src/Migrations/*', // 跳过 migration 文件
ForeachToInArrayRector::class, // 跳过某条具体规则
]);
第三方库生成的文件、数据库 migration、历史兼容层——加到 skip 列表就好。
接进 CI 流水线
最合理的用法不是本地偶尔跑一次,而是放进 CI,每次提交都检查:
# .github/workflows/rector.yml
name: Rector CI
on: [push, pull_request]
jobs:
rector:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: '8.4'
- run: composer install
- run: vendor/bin/rector process --dry-run
--dry-run 模式下有 diff 输出时返回非零退出码,CI 就会报错。也可以用快捷命令一键生成 CI 配置:
vendor/bin/rector setup-ci
Rector 基于 AST 工作,输出代码的格式可能不太好看(缩进、空行可能乱掉)。在它后面接一个格式化工具就行——ECS、PHP-CS-Fixer 或 Prettier for PHP 都可以。另外 PHP + HTML 混合的模板文件处理完后建议人工检查一遍。
Rector 核心包内置 142 条规则,加上社区扩展包总共有 840+ 条,覆盖 PHP 5.3 到 8.6 全版本升级和主流框架迁移。对于还在维护老项目或者追求代码质量的团队来说,这个工具值得放进工具链里。
参考资料

- https://github.com/rectorphp/rector
- https://getrector.com/
- https://getrector.com/find-rule — 浏览全部 840+ 条规则
原文标题: [rectorphp/rector]自动升级和重构你的PHP代码
原文地址: https://phpreturn.com/index/a6a1686bbb6205.html
原文平台: PHP武器库
版权声明: 本文由phpreturn.com(PHP武器库官网)原创和首发,所有权利归phpreturn(PHP武器库)所有,本站允许任何形式的转载/引用文章,但必须同时注明出处。