PHP-Parser 2.0 升级完全指南:从 1.x 迁移到 ParserFactory 时代
PHP-Parser 2.0 升级完全指南从 1.x 迁移到 ParserFactory 时代【免费下载链接】PHP-ParserA PHP parser written in PHP项目地址: https://gitcode.com/GitHub_Trending/ph/PHP-Parser导读本文以 PHP-Parser 官方升级文档 UPGRADE-2.0.md 为主体系统梳理从 1.x 迁移到 2.0 的全部破坏性变更与应对方案。读完本文你将掌握通过ParserFactory创建解析器的标准姿势、PHP 5/7 双版本解析模式的选择逻辑、Parser从类到接口的演进、旧别名清理带来的命名空间影响以及NodeTraverser、Node\Name、Scalar 节点等 API 的行为变化从而把存量代码安全、平滑地迁移到 2.0 及后续版本。适用前提本文所有结论以当前仓库PHP-Parser源码与官方文档为准。2.0 发布于 2015 年底见 CHANGELOG.md仓库现已演进到支持 PHP 8.x 的版本但 2.0 确立的工厂模式与版本化解析思想至今仍是该库的架构基石。一、PHP 版本要求运行环境与解析目标从此分离2.0 的一个关键前提变化是运行 PHP-Parser 本身需要 PHP 5.4 或更新版本被解析的源码仍可以是 PHP 5.2 / 5.3 语法只要运行环境更新即可。也就是说库的运行版本与代码的解析版本是两个独立维度。这个向前解析、向后兼容的设计在后续版本中被不断强化查看仓库当前的 PhpVersion.php 可见BUILTIN_TYPE_VERSIONS表中内置类型如array要求 5.1、callable要求 5.4、string要求 7.0、mixed要求 8.0都按版本号登记而 getNewestSupported() 目前已支持到 8.4。这正是一脉相承的按目标版本精确解析机制的雏形。二、创建解析器从直接new到ParserFactory2.0 之前解析器实例通过直接实例化获得但 2.0 中解析器类被改名旧写法已失效// 旧写法1.x已失效 use PhpParser\Parser, PhpParser\Lexer; $parser new Parser(new Lexer\Emulative);2.0 起必须经由ParserFactoryuse PhpParser\ParserFactory; $parser (new ParserFactory)-create(ParserFactory::PREFER_PHP7);create()的第一个参数决定如何处理不同的 PHP 版本共四个取值常量行为ParserFactory::PREFER_PHP7先按 PHP 7 解析失败再按 PHP 5 重试ParserFactory::PREFER_PHP5先按 PHP 5 解析失败再按 PHP 7 重试ParserFactory::ONLY_PHP7只按 PHP 7 解析ParserFactory::ONLY_PHP5只按 PHP 5 解析对绝大多数业务场景而言PREFER_PHP7与PREFER_PHP5的差别主要体现在标量类型提示的 AST 表示上按 PHP 7 解析时string这类标量类型提示存为字符串字面量string按 PHP 5 解析时存为new Name(string)节点对象。例如函数function foo(string $s) {}两种模式产生的Param-type分别是一个普通字符串和一个Name节点下游做 AST 遍历或改写时必须区分这两种形态。自定义词法分析器Lexer如需自定义 Lexer将其作为create()的第二个参数传入use PhpParser\ParserFactory; $lexer new MyLexer; $parser (new ParserFactory)-create(ParserFactory::PREFER_PHP7, $lexer);默认词法分析器是Lexer\Emulative它通过在 compatibility_tokens.php 中定义的兼容 token如 PHP 8 的T_MATCH、T_NULLSAFE_OBJECT_OPERATOR、T_ATTRIBUTEPHP 8.1 的T_ENUM、T_READONLYPHP 8.4 的T_PROPERTY_C等在旧版 PHP 上模拟新语法 token从而让旧运行环境也能解析新语法代码。2.0 时这一机制只覆盖 PHP 7 特性如今已扩展至 PHP 8.4。源码中的工厂模式演进当前仓库的 ParserFactory.php 已从 2.0 的四个常量 create()演进为版本对象 三种创建方法createForVersion(PhpVersion $version)按目标版本选择Lexer宿主版本直接用Lexer否则用Lexer\Emulative与解析器实现版本 ≥ 8.0 用Parser\Php8否则用Parser\Php7createForNewestSupportedVersion()创建支持最新版本当前为 8.4的解析器createForHostVersion()创建与运行环境版本一致的解析器不做任何 token 模拟。ParserFactoryTest.php 用assertInstanceOf(Php8::class, ...)验证了工厂会返回Parser\Php8实例。这印证了 2.0 引入的工厂 按版本选实现架构一直延续至今。三、PhpParser\Parser从类到接口的演进2.0 中PhpParser\Parser由具体类变为接口由以下三个类实现Parser\Php5PHP 5 语法解析器Parser\Php7PHP 7 语法解析器Parser\Multiple按策略在多个解析器间切换的复合解析器。同时解析器使用的 token 常量被移入Parser\Tokens。只要使用上文介绍的ParserFactory创建实例这些内部变动对你完全透明。当前仓库中该接口定义于 Parser.php核心方法为parse(string $code, ?ErrorHandler $errorHandler null): ?array——解析源码为语句节点数组默认错误处理器为ErrorHandler\Throwing抛异常若传入非抛异常的错误处理器且解析失败无法恢复则返回nullgetTokens(): array——返回上次解析产生的 token 列表。注意当前仓库已不再包含Parser\Php5与Parser\Multiple实现类仅剩 Php7.php 与 Php8.php多版本多实现的思路则由PhpVersion机制继承并发扬光大。四、遗留别名legacy aliases的移除2.0 清除了所有类名别名包括旧的非命名空间PHPParser_前缀类如PHPParser_Parser、PHPParser_Lexer等 1.x 时代不带命名空间的类为 PHP 7 支持而改名的类。迁移到 2.0 时请全局搜索代码中的PHPParser_前缀并替换为对应的PhpParser\命名空间类名同时同步调整相关use语句。这部分没有运行时兼容层属于硬性破坏性变更。五、弃用Node\Name的set系列方法Node\Name类的以下四个方法在 2.0 中被弃用set()setFirst()append()prepend()官方推荐的替代方案是静态方法Name::concat()与实例方法Name-slice()。从当前源码 Name.php 可以确认这两个 API 的设计细节slice(int $offset, ?int $length null)语义与array_slice()一致偏移与长度均可为负数返回与调用者同类型的新Name实例并保留属性空切片返回null且null会在concat()中被正确处理偏移越界时抛出\OutOfBoundsException。对常见情形offset 1 $length null还做了短路优化直接截取首个命名空间分隔符之后的部分。concat($name1, $name2, array $attributes [])拼接两个名字并返回新实例生成实例的类型取决于调用类如Name\FullyQualified::concat()得到全限定名。任一参数为null时返回另一方的副本两者皆为null时返回null——因此Name::concat($namespace, $shortName)这种命名空间可能为空的写法可以放心使用。示例把Foo\Bar\Baz去掉第一个段并追加Quuxuse PhpParser\Node\Name; $name new Name(Foo\Bar\Baz); $sliced $name-slice(1); // Bar\Baz $result Name::concat($sliced, Quux); // Bar\Baz\Quux六、杂项变更清单2.0 还有一批不集中归类但同样影响迁移的细节变更逐条说明如下1.NodeTraverser默认不再克隆节点1.x 中遍历器默认克隆所有节点2.0 起默认不克隆。若需恢复旧行为向构造函数传入trueuse PhpParser\NodeTraverser; $traverser new NodeTraverser(true); // 恢复克隆行为当前仓库的 NodeTraverser.php 构造函数签名已变为__construct(NodeVisitor ...$visitors)通过可变参数接收访问器不再支持布尔克隆开关——可见该开关在后继版本中已被彻底移除依赖默认克隆的代码应尽早改为显式使用 CloningVisitor 等方案。2. 移除遗留节点格式自定义节点需实现getSubNodeNames()2.0 删除了旧的节点格式。若你定义了自定义节点必须实现getSubNodeNames()方法返回子节点名字数组供遍历器识别其结构。仓库内置节点即以此为标准例如 Name::getSubNodeNames() 返回[name]。3.Scalar节点构造函数默认值被移除Scalar系列节点的构造函数不再提供默认值。旧写法new LNumber()必须改为显式传值use PhpParser\Node\Scalar\LNumber; $node new LNumber(0); // 而非 new LNumber()同理其余标量节点如String_、DNumber等构造时也需显式提供值。4. 双引号字符串encapsed string的parts表示变更双引号内插字符串中非变量片段此前以原始字符串表示2.0 起改为使用Scalar\EncapsStringPart节点。这影响两个节点的parts子节点Scalar\Encaps已演进为 InterpolatedString.phpExpr\ShellExecshell 执行字符串。即类似hello $name的 AST 中hello 不再是一个普通字符串而是一个EncapsStringPart节点。遍历或重建此类 AST 时必须把parts里的每一项都当作节点对象处理而不是混用字符串与节点。该变更在 CHANGELOG.md 中有对应记录印证了它是 2.0.0 正式版的既定行为。当前仓库中 EncapsedStringPart.php 已作为兼容类存在实际实现委托给 InterpolatedStringPart.php。七、迁移清单速查将 1.x 代码迁移到 2.0按以下顺序检查即可运行环境确认 PHP ≥ 5.4方可运行 PHP-Parser 2.0实例化方式将new Parser(...)全部替换为(new ParserFactory)-create(...)并按需选择PREFER_PHP7/PREFER_PHP5/ONLY_PHP7/ONLY_PHP5四个模式之一自定义 Lexer 作为第二参数传入命名空间删除所有PHPParser_遗留别名引用改用PhpParser\命名空间类Node\Name把set()、setFirst()、append()、prepend()改写为concat()与slice()注意slice()空切片返回null的语义NodeTraverser确认是否需要new NodeTraverser(true)恢复克隆行为自定义节点补上getSubNodeNames()方法标量节点new LNumber()等写法补上显式初始值encapsed 字符串遍历Scalar\Encaps/Expr\ShellExec的parts时按节点对象而非原始字符串处理EncapsStringPart。按此清单逐项处理即可完成从 1.x 到 2.0 的平滑迁移并为后续 3.0、4.0、5.0 的升级可分别参考 UPGRADE-3.0.md、UPGRADE-4.0.md、UPGRADE-5.0.md打下基础——2.0 引入的ParserFactory与运行/解析版本分离思想正是理解该库全部后续演进的关键起点。【免费下载链接】PHP-ParserA PHP parser written in PHP项目地址: https://gitcode.com/GitHub_Trending/ph/PHP-Parser创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联
返回资讯列表 →