> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# PHP AST

> 說明 PHP AST（抽象語法樹）的基本、PHP 內部 AST 的定位，並解說使用 nikic/PHP-Parser 進行實務程式碼解析與轉換的模式。

## 什麼是 PHP AST

AST（Abstract Syntax Tree／抽象語法樹）是將原始碼分解為「具有意義的結構」的樹狀結構。

當您要建立程式碼生成工具、靜態分析工具或自動重構工具時，AST 幾乎是必要基礎。即便是字串替換容易出錯的變換，透過 AST 也能以「function call」、「class declaration」、「`use` statement」這類語法單位安全處理。

```mermaid theme={null}
graph TD
    A["<?php<br>echo add(1, 2);"] --> B["Stmt\\Expression"]
    B --> C["Expr\\FuncCall: add"]
    C --> D["Arg: 1"]
    C --> E["Arg: 2"]
```

## PHP 的內部 AST

PHP 7 以後的 Zend Engine 並非直接將 PHP 程式碼轉為 opcode，而是先轉為內部 AST 再進行編譯。

* 即使是以 `php -r` 執行的單行程式碼，內部也走相同的編譯管道
* OPcache 會快取並重複使用最終產生的 opcode
* 於一般應用程式開發中，幾乎沒有直接操作此內部 AST 的機會

因此當您想「以 AST 編輯程式碼」時，實務上使用可在使用者空間操作的 AST 函式庫較為實用，而非 PHP 執行時內部 AST。

## nikic/PHP-Parser 套件

[nikic/PHP-Parser](https://github.com/nikic/PHP-Parser) 是用於解析、走訪、再生 PHP 程式碼為 AST 的標準函式庫。README 與官方文件說明其基本步驟有下列 3 步。

### 安裝

```bash theme={null}
composer require nikic/php-parser
```

### 基本 parse 範例（將程式碼轉為 AST）

```php theme={null}
<?php

use PhpParser\Error;
use PhpParser\ParserFactory;

$code = <<<'CODE'
<?php
function greet(string $name): void {
    echo "Hello, {$name}";
}
CODE;

$parser = (new ParserFactory())->createForNewestSupportedVersion();

try {
    $ast = $parser->parse($code);
} catch (Error $error) {
    echo "Parse error: {$error->getMessage()}\n";
    return;
}
```

### 以 NodeVisitor 模式走訪、變更 AST

```php theme={null}
<?php

use PhpParser\Node;
use PhpParser\NodeTraverser;
use PhpParser\NodeVisitorAbstract;

$traverser = new NodeTraverser();

$traverser->addVisitor(new class extends NodeVisitorAbstract {
    public function leaveNode(Node $node)
    {
        if ($node instanceof Node\Scalar\Int_) {
            return new Node\Scalar\String_((string) $node->value);
        }
    }
});

$modifiedAst = $traverser->traverse($ast);
```

繼承 `NodeVisitorAbstract` 後，只需實作所需的 hook（`enterNode` / `leaveNode`）。於複雜的變換中，於 `enterNode` 蒐集資訊、於 `leaveNode` 置換的流程頗為有效。

### 以 Pretty Printer 再生程式碼

```php theme={null}
<?php

use PhpParser\PrettyPrinter;

$prettyPrinter = new PrettyPrinter\Standard();
$newCode = $prettyPrinter->prettyPrintFile($modifiedAst);

echo $newCode;
```

透過此流程，可以將「作為語法樹的程式碼」而非「作為字串的程式碼」作為變換對象。

## Laravel/Chisel 的使用範例

[laravel/chisel](/zh-TW/blog/chisel-introduction) 是於後處理階段裁剪啟動套件的函式庫。於 `composer.json` 中將 `nikic/php-parser` 5.x 系列納入相依。

於 Chisel 的 `Laravel\Chisel\Ast\Source` 中，以下列步驟執行 AST 編輯。

1. 以 `ParserFactory::createForNewestSupportedVersion()` 解析程式碼
2. 於 `NodeTraverser` 加入多個 Visitor（如 `RemoveImportVisitor`）進行變換
3. 以 `PhpParser\PrettyPrinter\Standard` 的 `printFormatPreserving()` 保持原格式並寫回

透過此設計，可以基於語法而非文字替換，安全地刪除 `use` 敘述、trait、interface。

## 使用情境

使用 AST 較容易推動下列開發。

* 程式碼生成 CLI（於樣板建立後以語法單位編輯）
* 靜態分析工具（偵測特定語法、偵測違反規則）
* 自動重構（API 遷移或命名變更的半自動化）
* 專案範本的後處理（如 Chisel 的功能刪除、置換）

## 總結

PHP AST 於一般 Laravel 應用程式開發並非每日使用的功能。

不過若您要推動工具開發或套件開發，AST 是實現「不易損壞且具再現性的程式碼變更」的強力基礎。首先請以小型 CLI 試用 `nikic/php-parser` 的 Parser、Visitor、Pretty Printer 三者。


## Related topics

- [Laravel Chisel — 啟動套件的安裝後腳本函式庫](/zh-TW/blog/chisel-introduction.md)
- [PHP Attributes](/zh-TW/advanced/php-attributes.md)
- [PHP FFI](/zh-TW/advanced/ffi.md)
- [VOICEVOX Core for PHP](/zh-TW/packages/voicevox-core-php/index.md)
- [PHP Reflection API](/zh-TW/advanced/php-reflection.md)
