1. 程式人生 > >PHP Tokenizer 學習筆記

PHP Tokenizer 學習筆記

簡述

在某個專案中需要分析 PHP 程式碼,分離出對應的函式呼叫(以及原始碼對應的位置)。雖然這使用正則也可以實現,但無論從效率還是程式碼複雜度方面考慮,這都不是最優的方式。

查詢了 PHP 手冊,發現其實 PHP 已經內建解析器的介面,那就是 PHP Tokenizer ,這工具正是我想要的。使用 PHP Tokenizer 能簡單、高效、準確的分析出 PHP 原始碼的組成。

例項

官方站點對 Tokenizer 的文件很少,不過這不影響我們理解它。Tokenizer 元件僅僅包含兩個函式: token_get_all 以及token_name ,它們分別用於分析 PHP 程式碼以及獲取程式碼對應的識別符號名稱。

下面是個簡單的例項,說明如何使用這兩個函式:

$code = '<?php echo "string1"."string2"; ?>';
$tokens = token_get_all($code);
foreach ($tokens as $token) {
    if (is_array($token)) {
        // 行號、識別符號字面量、對應內容
        printf("%d - %s\t%s\n", $token[2], token_name($token[0]), $token[1]);
    }
}

對應的輸出為

1 - T_OPEN_TAG    <?php 
1 - T_ECHO    echo
1 - T_WHITESPACE     
1 - T_CONSTANT_ENCAPSED_STRING    "string1"
1 - T_CONSTANT_ENCAPSED_STRING    "string2"
1 - T_WHITESPACE     
1 - T_CLOSE_TAG    ?>

這裡順便說明下,$token 如果為陣列,那麼分別對應的三個陣列成員為 token 識別符號(可以用 token_name 獲得字面量)、對應的原始碼內容、以及對應的行號。

還有中情況就是 $token 為字串,這可能的情況之一就是為 T_CONSTANT_ENCAPSED_STRING 等常量,在分析程式碼時要注意。如果對這點很在意,可以考慮使用 這裡的程式碼 。

是的,呼叫方式非常的簡單,我們的野心當然遠遠要比寫個簡單的迴圈要大得多。我們可以利用這個元件做寫實事,例如下面的程式碼用於「壓縮」 PHP 程式碼,去除不不要的換行、空白以及註釋

/**
 * 「壓縮」PHP 原始碼
 *
 * @see http://c7y.phparch.com/c/entry/1/art,practical_uses_tokenizer
 */
class CompactCode
{
    static protected $out;
    static protected $tokens;

    static public function compact($source)
    {
        // 解析 PHP 原始碼
        self::$tokens = token_get_all($source);   
        self::$out = '';

        reset(self::$tokens);

        // 遞迴判斷每個標記符的型別
        while ($t = current(self::$tokens)) {
            if (is_array($t)) {
                // 過濾空白、註釋
                if ($t[0] == T_WHITESPACE || $t[0] == T_DOC_COMMENT || $t[0] == T_COMMENT) {
                    self::skipWhiteAndComments();
                    continue;
                }       
                self::$out .= $t[1];
            } else {
                self::$out .= $t;
            }

            next(self::$tokens);
        }

        return self::$out;
    }

    static private function skipWhiteAndComments()
    {
        // 增加個空格,用於分割關鍵字
        self::$out .= ' ';
        while ($t = current(self::$tokens)) {
            // 再次貪婪查詢
            if (is_array($t) && ($t[0] == T_WHITESPACE || $t[0] == T_DOC_COMMENT || $t[0] == T_COMMENT)) {
                next(self::$tokens);
            } else {
                return;
            }
        }
    }
}

呼叫方式很簡單,只需要使用

CompactCode::compact($source_code);

即可,返回的字串就是壓縮以後的內容。