快轉到主要內容

PHP 神奇的 token_get_all() 函式|解析 PHP 檔案內部結構

·1147 字· loading · loading ·
Computer-Science PHP
目錄
透過 PHP Lexer 來解析檔案內部的程式。

前言
#

最近在改之前寫的 PHP 小框架專案「AlpacaPHP」,路由的部分我希望能夠讓頁面可以自行在檔案中宣告要接受哪些 URI 參數,例如:

1Router::args($action, $id);

當然也可以不宣告表示沒有接收參數的需求。
我所預期的效果是,若檔案不需要參數,但使用者傳了,那路由就直接 drop 掉,而不是依舊導進頁面。

但實作中卻遇到一個阻礙 - include 前我怎麼知道該檔案是否需要參數?

路由在 include 檔案之前需要先知道檔案的參數要求;
想知道檔案的參數要求就需要先 include 它。
至此,流程成了套娃。


神奇的 Function 「token_get_all()
#

這個 function 可以理解成就是 PHP 內部的 Lexer (又稱 Scanner, Tokenizer)。
是用來將 PHP 程式碼標記成 Tokens 的程式,這意味著透過它,你能真正地知道檔案中有那些變數、functions。

你當然也可以自己寫一套流程解析檔案,但我想應該不會比 PHP 提供的 Lexer 來的更有效。
畢竟如果單純比對文字的話,那可能有註解內容被誤判的問題,因為你的程式不知道那是註解但 token_get_all() 知道


使用方法
#

該 function 可以輸入程式碼文字,例如token_get_all('<?php echo $a;'),接著它會返回解析的 tokens,類型是陣列,內容大致如下:

 1Array
 2(
 3    [0] => Array
 4        (
 5            [0] => 397          // T_OPEN_TAG
 6            [1] => <?php        // 原始程式字碼
 7            [2] => 1            // 行號
 8        )
 9    [1] => Array
10        (
11            [0] => 328          // T_ECHO
12            [1] => echo
13            [2] => 1
14        )
15    [2] => Array
16        (
17            [0] => 393          // T_WHITESPACE (空格)
18            [1] =>  
19            [2] => 1
20        )
21    [3] => Array
22        (
23            [0] => 320          // T_VARIABLE
24            [1] => $a
25            [2] => 1
26        )
27    [4] => ;                    // 簡單符號直接以字串呈現
28)

最外層每個 item 代表一個 token。

每個 token 的內容通常會有三個 items:

  • index 0:表示 token 的名稱。(但它是以整數表示,可以透過 token_name() 轉換成名稱字串,或者使用 PHP 的常數比對,例如 320 表示為 variable 的 T_VARIABLE 等)。
  • index 1:表示原始的程式字碼
  • index 2:表示該 token 所在的行號

回到剛剛說的,要判斷是否呼叫了 Router::args()
我需要先判斷文字(T_STRING)「Router」;
接著判斷後面是否跟隨著雙冒號(T_DOUBLE_COLON)「::」;
再判斷後面是否又跟隨著文字(T_STRING)「args」;
最後判斷是否又跟隨著純字串「(」表示呼叫 function。

搭配 function file_get_contents() 讀取檔案,最終我的路由判斷方式如下:

 1# magic function for check if file calls Router::args()
 2static function getArgsCount($file) {
 3    $code = file_get_contents($file);
 4    $tokens = token_get_all($code);
 5    $count = null;
 6    for ($i = 0; $i < count($tokens); $i++) {
 7        // 找到 Router::args(
 8        if (
 9            is_array($tokens[$i]) && $tokens[$i][0] === T_STRING && $tokens[$i][1] === 'Router' &&
10            isset($tokens[$i+1]) && $tokens[$i+1][0] === T_DOUBLE_COLON &&
11            isset($tokens[$i+2]) && $tokens[$i+2][0] === T_STRING && $tokens[$i+2][1] === 'args' &&
12            isset($tokens[$i+3]) && $tokens[$i+3] === '('
13        ) {
14            // 計算括號內的參數數量
15            $j = $i + 4;
16            $args = 0;
17            $parenLevel = 1;
18            while ($parenLevel > 0 && isset($tokens[$j])) {
19                if ($tokens[$j] === '(') $parenLevel++;
20                elseif ($tokens[$j] === ')') $parenLevel--;
21                elseif ($parenLevel === 1 && $tokens[$j] === ',') $args++;
22                $j++;
23            }
24            // 有參數才算
25            if ($j > $i + 4) $count = $args + ($j - $i - 4 > 1 ? 1 : 0);
26            break;
27        }
28    }
29    return $count;
30}

References
#

Alpaca
作者
Alpaca
No one can stop my feet.

相關文章

PHP MySQL query with 「PDO」
·332 字· loading · loading
Computer-Science PHP SQL PDO MySQL
Don’t continue to use original MySQL functions in PHP, let’s try PDO !
如何在 ZSH 遇到 Command not found 時顯示安裝提示?
·248 字· loading · loading
Computer-Science ZSH
讓你的 ZSH 也可以貼心的提示指令安裝包怎麼下載。
架設暗網 - Tor Hidden Service
·1127 字· loading · loading
Computer-Science Tor
本文將說明如何架設 Tor 暗網,將你的服務隱匿在 Tor 當中。
《Pwn - 0x00》Hello, Pwn!
·674 字· loading · loading
Computer-Science Pwn
計算機領域中的「Pwn」是什麼意思?
Ubuntu 睡眠(Suspend)、休眠(Hibernate) 差別,及啟用休眠的方式
·1625 字· loading · loading
Computer-Science Ubuntu Hibernate
讓電腦可以不消耗電力的存放記憶體的狀態,下次開機將會接續上次的工作進度。
Git log 中文亂碼?
·504 字· loading · loading
Computer-Science Git
某些環境不支援顯示中文的 git log,其實只要修改系統的語系即可。