
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),僅供參考