ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Rector 升级指南:从 1.x 迁移到 2.x 的自定义规则改写实战(FileNode / AbstractRector / ScopeFetcher)

Rector 升级指南:从 1.x 迁移到 2.x 的自定义规则改写实战(FileNode / AbstractRector / ScopeFetcher) Rector 升级指南从 1.x 迁移到 2.x 的自定义规则改写实战FileNode / AbstractRector / ScopeFetcher【免费下载链接】rectorInstant Upgrades and Automated Refactoring of any PHP 5.3 code项目地址: https://gitcode.com/GitHub_Trending/re/rector本指南基于当前仓库根目录的 UPGRADING.md系统梳理 Rector 从 1.x 升级到 2.0、以及从 2.2.14 升级到 2.3 过程中对自定义规则开发者的破坏性变更包括FileWithoutNamespace到FileNode的迁移、beforeTraverse()的弃用、AbstractScopeAwareRector的移除与ScopeFetcher的引入、getRuleDefinition()的废弃以及SetListInterface的删除。读完本文你将能够把基于旧 API 编写的自定义 Rector 规则一键迁移到 2.x 架构并理解FileNode在文件级改造如自动添加declare(strict_types1)中的底层工作方式。升级总览两个版本跨度三类变更Rector 的升级路径分为两个阶段分别对应不同的破坏性变更2.2.14 → 2.3聚焦节点抽象层重构核心是FileWithoutNamespace被FileNode取代beforeTraverse()生命周期方法被冻结。1.x → 2.0底层解析与静态分析引擎全面换代PHP-Parser 5、PHPStan 2、PHP 7.4 运行门槛同时对自定义规则 API 做了三处简化移除AbstractScopeAwareRector、移除强制的getRuleDefinition()、移除SetListInterface。下面逐一展开并在每一节结合当前仓库源码验证文档所述行为。一、2.2.14 → 2.3FileNode取代FileWithoutNamespace1.1 变更背景在 2.2.14 及之前Rector 使用Rector\PhpParser\Node\FileWithoutNamespace表示“没有命名空间的文件”它只能承载无命名空间文件的顶层语句。这带来两个问题有命名空间的文件与无命名空间的文件被分到两种节点模型中规则编写者必须区分处理修改文件顶层结构例如插入declare(strict_types1)时两种文件各有一套逻辑。变更内容FileWithoutNamespace已废弃由FileNodesrc/PhpParser/Node/FileNode.php取代。FileNode同时表示有命名空间和无命名空间的文件并且允许直接修改文件内部的语句stmts。1.2beforeTraverse()被标记为final文档明确beforeTraverse()现在被标记为final规则作者不应再覆写它而应改用getNodeTypes()配合FileNode::class来声明对文件级节点的兴趣。这一点在源码中得到直接印证src/Rector/AbstractRector.php 中beforeTraverse()已被声明为final且仅返回null/** * return Node[]|null * * internal */ final public function beforeTraverse(array $nodes): ?array { return null; }同时RectorNodeTraverser 的traverse()会在进入节点遍历前调用每个 visitor 的beforeTraverse()若返回非null则替换整个节点数组——这正是旧代码中“节点 hacking”所依赖的入口。如今该入口被冻结所有文件级操作都必须收敛到refactor()内完成。1.3 迁移示例从beforeTraverse到FileNode::refactor文档给出的迁移前后对比是理解这次变更的最佳素材。迁移前旧写法use Rector\PhpParser\Node\FileWithoutNamespace; use Rector\Rector\AbstractRector; final class SomeRector extends AbstractRector { public function getNodeTypes(): array { return [FileWithoutNamespace::class]; } public function beforeTraverse(array $nodes): array { // some node hacking } /** * param FileWithoutNamespace $node */ public function refactor(Node $node): ?Node { // ... } }迁移后新写法不再覆写beforeTraverse()getNodeTypes()返回[FileNode::class]所有逻辑在refactor()中通过操作$node-stmts完成。以“给文件顶部插入declare(strict_types1)”为例use Rector\PhpParser\Node\FileNode; use Rector\Rector\AbstractRector; final class SomeRector extends AbstractRector { public function getNodeTypes(): array { return [FileNode::class]; } /** * param FileNode $node */ public function refactor(Node $node): ?Node { foreach ($node-stmts as $stmt) { // check if has declare_strict already? // ... // create it $declareStrictTypes $this-createDeclareStrictTypesNode(); // add it $node-stmts array_merge([$declareStrictTypes], $node-stmts); } return $node; } }注意这里$node-stmts是FileNode的公共可写属性见 FileNode.php直接对它做array_merge即可改变文件顶层结构返回$node后遍历器会将其写回。1.4 同时处理命名空间文件与无命名空间文件由于FileNode同时覆盖两种文件若要操作“文件内首个语句块”需要同时挂钩两个节点FileNode处理无命名空间文件与PhpParser\Node\Stmt\Namespace_处理有命名空间文件use Rector\PhpParser\Node\FileNode; use Rector\Rector\AbstractRector; use PhpParser\Node\Stmt\Namespace_; final class SomeRector extends AbstractRector { public function getNodeTypes(): array { return [FileNode::class, Namespace_::class]; } /** * param FileNode|Namespace_ $node */ public function refactor(Node $node): ?Node { if ($node instanceof FileNode $node-isNamespaced()) { // handled in the Namespace_ node return null; } foreach ($node-stmts as $stmt) { // modify stmts in desired way here } return $node; } }关键点在于FileNode::isNamespaced()FileNode.php它遍历$stmts若发现任意Namespace_子节点则返回true。上面的守卫逻辑保证有命名空间的文件交给Namespace_节点处理无命名空间的文件才在FileNode中处理二者互不重复。1.5 从源码看FileNode的能力边界FileNode并不仅仅是一个“语句容器”从 FileNode.php 的实现可以看到它为文件级改造提供了一整套能力isNamespaced(): bool判断文件是否包含Namespace_节点L186-L196。getNamespace(): ?Namespace_当文件中恰好只有一个命名空间时返回它否则返回nullL197-L205。getUses()/getUsesAndGroupUses()收集文件根部的use语句含GroupUseL209-L227。addImports()/removeImports()在文件或命名空间内增删 use 导入并自动去重、维护UsedImports追踪状态L59-L109、L150-L170。getType()返回Stmt_FileNode配合BetterStandardPrinter::pStmt_FileNode()完成打印L172-L178。此外src/PhpParser/Enum/NodeGroup.php 的STMTS_AWARE常量将FileNode与ClassMethod、Function_、Namespace_等并列意味着遍历器会对它按“含Stmt[] $stmts公共属性”的节点统一处理——这是它能够承载文件级改写的基础保障。二、从 1.x 升级到 2.0环境与底层引擎换代2.0 是一次底层大版本跃迁先满足运行环境要求再谈规则迁移。2.1 PHP 版本要求Rector 现在需要PHP 7.4 或更高版本才能运行。这主要是为了支撑 PHP-Parser 5 与 PHPStan 2 对解析和静态分析能力的新要求也意味着 CI 中运行 Rector 的环境需同步提升。2.2 解析引擎PHP-Parser 5Rector 2.0 底层切换到 PHP-Parser 5。这对规则作者的影响主要体现在节点 API 层面——部分PhpParser\Node类的构造方式、子节点命名sub node names或遍历行为可能有细微差异。若你的自定义规则直接操作底层 AST 节点升级后应重点检查节点构造参数与属性名是否与 PHP-Parser 5 对齐例如DeclareItem、PropertyItem等新拆分出的节点类型见 vendor/nikic/php-parser/lib/PhpParser/Node 目录自定义NodeVisitor与NodeTraverser的交互是否仍符合预期。官方针对 PHP-Parser 有独立的 5.0 升级指南见 UPGRADING.md 中引用的上游文档迁移时应一并对照。2.3 静态分析引擎PHPStan 2Rector 的类型系统构建在 PHPStan 之上升级到 PHPStan 2 后类型 API 可能发生变化。规则中若直接引用PHPStan\Type\*、PHPStan\Analyser\Scope等类型应确认所用方法与 PHPStan 2 的签名兼容。这也是下面ScopeFetcher新接口出现的重要背景之一。三、自定义规则作者的三大迁移点2.0 破坏性变更这是从 1.x 升级到 2.0 时规则作者必须逐一处理的三个 API 变化。3.1AbstractScopeAwareRector移除改用AbstractRectorScopeFetcherRector\Rector\AbstractScopeAwareRector在 2.0 中被彻底移除。该类的设计初衷是让规则直接拿到Scope但官方认为“为取一个辅助对象而多一层抽象”让自定义规则创建变得含糊且复杂。迁移前use Rector\Rector\AbstractScopeAwareRector; final class SimpleRector extends AbstractScopeAwareRector { public function refactorWithScope(Node $node, Scope $scope): ?Node { // ... } }迁移后继承标准的AbstractRector仅在确实需要时通过ScopeFetcher取Scopeuse Rector\Rector\AbstractRector; use Rector\PHPStan\ScopeFetcher; final class SimpleRector extends AbstractRector { public function refactor(Node $node): ?Node { if (...) { // this allow to fetch scope only when needed $scope ScopeFetcher::fetch($node); } // ... } }ScopeFetcher的实现非常轻量src/PHPStan/ScopeFetcher.php它从节点的SCOPEattribute 中取出MutatingScope并作为PHPStan\Analyser\Scope返回若节点上没有可用的 Scope例如改动后的新节点未刷新 scope会抛出ShouldNotHappenException提示“先修复变更节点的 scope 刷新”。这提醒我们只有在修改前读取原有节点的类型信息时才应调用fetch()对于新建节点不要依赖 scope。3.2getRuleDefinition()不再是必选1.x 时代每个规则都强制实现getRuleDefinition(): RuleDefinition返回描述与代码示例用于文档生成与规则检索。但实际上很多本地自定义规则只是草草填个空壳纯粹为了“让 Rector 高兴”。2.0 中getRuleDefinition()方法已从AbstractRector移除规则作者不再需要它use Rector\Rector\AbstractRector; -use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; -use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; final class SimpleRector extends AbstractRector { - public function getRuleDefinition(): RuleDefinition - { - return new RuleDefinition(// todo fill the description, [ - new CodeSample( - CODE_SAMPLE -// todo fill code before -CODE_SAMPLE - , - CODE_SAMPLE -// todo fill code after -CODE_SAMPLE - ), - ]); - } // valuable code here }值得注意的是核心规则进入官方规则集仍然保留getRuleDefinition()——例如 rules/TypeDeclaration/Rector/StmtsAwareInterface/DeclareStrictTypesRector.php 就仍带有RuleDefinition与CodeSample。这是因为官方规则需要文档便于开发者搜索理解而本地自定义规则不再被强制要求。文档建议如果你担心几个月后看不懂自己的规则就把说明写在类上方的 docblock 里——这是最朴素可靠的文档位置。3.3SetListInterface删除SetListInterfaceRector\Set\Contract\SetListInterface作为一个已废弃的接口在 2.0 中被彻底删除。如果你自定义过 set list只需把接口摘掉即可-use Rector\Set\Contract\SetListInterface; -final class YourSetList implements SetListInterface final class YourSetList在当前仓库的 src/Set/Contract 与 src/Set/Enum 目录中已找不到该接口的踪迹证实其确实已从代码库移除。config/set/下的各 PHP 版本与功能 set 文件如 config/set/code-quality.php、config/set/php81.php现在都以普通常量数组形式组织不再依赖该接口。四、实战验证仓库内真实使用FileNode的规则UPGRADING.md 中的示例并非空谈——当前仓库已经存在一个真实规则几乎就是文档示例的生产版本rules/TypeDeclaration/Rector/StmtsAwareInterface/DeclareStrictTypesRector.php“Adddeclare(strict_types1)if missing in a namespaced file”。该规则的关键实现与文档示例一一对应/** * param FileNode $node */ public function refactor(Node $node): ?FileNode { // shebang files cannot have declare strict types if ($this-getFile()-hasShebang()) { return null; } // only add to namespaced files, as global namespace files are often included in other files if (!$node-isNamespaced()) { return null; } // when first stmt is Declare_, verify if there is strict_types definition already, // as multiple declare is allowed, with declare(strict_types1) only allowed on very first stmt if ($this-declareStrictTypeFinder-hasDeclareStrictTypes($node)) { return null; } $declaresStrictType $this-nodeFactory-createDeclaresStrictType(); $node-stmts array_merge([$declaresStrictType, new Nop()], $node-stmts); return $node; } /** * return arrayclass-stringNode */ public function getNodeTypes(): array { return [FileNode::class]; }它演示了文件级规则的标准套路也补充了 UPGRADING.md 示例未覆盖的工程细节前置守卫hasShebang()跳过 shebang 脚本文件declare必须位于文件首行shebang 会与之冲突isNamespaced()排除无命名空间文件全局命名空间文件常被 include 进其他文件加strict_types可能影响包含方。去重检查通过DeclareStrictTypeFinder确认首条语句不是已有的declare(strict_types1)——PHP 允许重复declare但strict_types指令只允许出现在第一条语句。语句插入用$this-nodeFactory-createDeclaresStrictType()构造节点再以array_merge([$declaresStrictType, new Nop()], $node-stmts)插到stmts头部中间补一个Nop空行节点保证格式美观。这套“守卫 → 去重 → 构造 → 前置合并 → 返回节点”的模式是所有FileNode文件级规则的推荐写法可参考 rules/TypeDeclaration/Rector/StmtsAwareInterface/SafeDeclareStrictTypesRector.php 与 vendor/rector/rector-phpunit/rules/CodeQuality/Rector/StmtsAwareInterface/DeclareStrictTypesTestsRector.php 查看更多同族实现。五、迁移清单与常见陷阱将以上变更整理成一份可勾选的迁移清单检查项1.x 写法2.x 写法依据文件级节点FileWithoutNamespaceFileNodeUPGRADING.md、FileNode.php文件首部 hook覆写beforeTraverse()getNodeTypes()返回FileNode::classAbstractRector.php获取 ScopeAbstractScopeAwareRector::refactorWithScope()ScopeFetcher::fetch($node)ScopeFetcher.php规则说明强制getRuleDefinition()可选或用类 docblock核心规则仍保留见 DeclareStrictTypesRector自定义 set listimplements SetListInterface纯常量数组src/Set/Contract运行环境—PHP ≥ 7.4、PHP-Parser 5、PHPStan 2UPGRADING.md需要特别留意的三个陷阱ScopeFetcher::fetch()的时机它依赖节点上的SCOPEattribute由 Rector 的NodeScopeAndMetadataDecorator在解析阶段注入。对刚创建、尚未刷新 scope 的节点调用会直接抛异常因此只应在“读取既有节点”时使用。FileNode与Namespace_的去重协作有命名空间文件同时会命中FileNode与Namespace_两个节点务必像 1.4 节那样用isNamespaced()做守卫否则同一份 stmts 会被处理两次。declare(strict_types1)的位置约束它必须是文件第一条语句插入时若文件已有其他declare需先校验首个语句参考DeclareStrictTypesRector的去重逻辑。结语Rector 2.x 的升级方向非常清晰简化规则作者的心智负担——用统一的FileNode替代两种文件模型用按需的ScopeFetcher替代强制的 scope 注入用可选的 docblock 替代空洞的RuleDefinition并彻底清理已废弃的SetListInterface。对自定义规则开发者而言迁移工作量集中在本文列出的几处机械改动上而对文件级规则自动插入declare、调整 use 导入、统计顶层语句等的编写者来说FileNode提供的isNamespaced()、addImports()、removeImports()等能力让这类改造第一次有了正式、稳定的入口。建议在升级后对照文末清单逐项检查并以仓库中DeclareStrictTypesRector等规则为模板重写自己的文件级规则。【免费下载链接】rectorInstant Upgrades and Automated Refactoring of any PHP 5.3 code项目地址: https://gitcode.com/GitHub_Trending/re/rector创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表