本文目录导读:

在 PHP 项目中,token_get_all() 是一个非常核心且强大的函数,它直接对应了 词法分析(Lexical Analysis)阶段的核心工作。
token_get_all() PHP 引擎内置的“词法分析器”,下面我会详细解释它的原理、在项目中的实际用途以及注意事项。
词法分析(Lexical Analysis)是什么?
在编译器/解释器中,词法分析是第一步。
- 输入:原始的源代码字符串(
<?php echo "Hello";)。 - 输出:一个词法单元流(Token Stream),即将源代码拆分成一个个有意义的“单词”或“符号”。
- 作用:把字符序列变成 Token 序列,同时丢弃空白、注释等无关内容,这个阶段不关心语法结构是否合理,只关心每个“单词”是什么。
token_get_all() 的工作原理
token_get_all() 直接将 PHP 源代码字符串解析成一个包含了所有 token 的数组。
函数签名:
token_get_all(string $code, int $flags = 0): array
返回值示例:
假设我们有以下 PHP 代码:
<?php $a = 1 + 2;
调用 token_get_all('<?php $a = 1 + 2;'); 后,会返回一个数组,数组每个元素可能是:
- 单个字符串:代表一个简单的、单字符的 token,、、、 等,这些是“孤立的”符号。
- 索引数组:代表一个复合的 token,包含三个元素:
- Token ID (整数):使用
token_name()可以将其转换为字符串(如T_OPEN_TAG,T_VARIABLE,T_LNUMBER)。 - 原始字符串:匹配到的源代码片段(如
'$a'、'1')。 - 行号 (整数):该 token 在原代码中所在的起始行。
- Token ID (整数):使用
其输出结构类似于:
[
[T_OPEN_TAG, '<?php ', 1],
[T_VARIABLE, '$a', 1],
'=', // 单字符 token
[T_LNUMBER, '1', 1],
'+', // 单字符 token
[T_LNUMBER, '2', 1],
';' // 单字符 token
]
在 PHP 项目中的应用场景
token_get_all() 在 PHP 项目中并非日常业务开发常用,但在底层工具、代码分析、元编程等领域是不可或缺的。
静态代码分析与代码质量工具
这是最常见的场景,像 PHP-CS-Fixer(代码格式修复工具)、PHPStan、Psalm(静态分析工具)、Phan 等工具,都需要先将代码解析成 tokens,才能进行下一步的抽象语法树(AST,Abstract Syntax Tree)构建或直接进行规则匹配。
-
示例:检测代码中是否使用了
eval()function containsEval(string $code): bool { $tokens = token_get_all($code); foreach ($tokens as $token) { if (is_array($token) && $token[0] === T_EVAL) { return true; } } return false; } -
示例:检测是否使用了
require_oncefunction hasRequireOnce(string $code): bool { $tokens = token_get_all($code); foreach ($tokens as $token) { if (is_array($token) && $token[0] === T_REQUIRE_ONCE) { return true; } } return false; }这比用正则匹配
require_once要可靠得多,可以避免匹配到字符串或注释中的内容。
代码混淆器 / 格式化器 / 最小化工具
你可以遍历 token 数组,根据 token 类型进行修改,然后重新组装代码。
- 示例:移除所有注释
function removeComments(string $code): string { $tokens = token_get_all($code); $cleanCode = ''; foreach ($tokens as $token) { if (is_array($token)) { // 跳过注释 if (in_array($token[0], [T_COMMENT, T_DOC_COMMENT])) { continue; } $cleanCode .= $token[1]; } else { $cleanCode .= $token; } } return $cleanCode; }
语法高亮与代码编辑器
虽然 PHP 有 highlight_file() 和 highlight_string(),但如果你需要高度自定义的语法高亮(例如只在特定标签上添加效果),token_get_all() 可以提供精确的控制,你可以根据 token 类型输出不同的 HTML 标签或 CSS 类。
领域特定语言(DSL,Domain-Specific Language)预处理或模板引擎
如果项目在标准 PHP 代码中嵌入了自定义的标记(例如类似 Laravel Blade 中的 @if、@foreach),可以在 token_get_all() 的基础上进行扩展,识别并转换这些自定义语法为标准的 PHP 语法。
关键 Token 类型参考(部分)
- T_NAMESPACE, T_USE, T_CLASS, T_FUNCTION, T_VARIABLE:结构定义。
- T_IF, T_ELSE, T_FOR, T_WHILE:控制结构。
- T_ECHO, T_RETURN, T_NEW:语言构造。
- T_STRING:非保留字的标识符(例如函数名、类名)。
- T_CONSTANT_ENCAPSED_STRING:单引号或双引号字符串。
- T_DOUBLE_ARROW:
=>(数组键值对)。 - T_PAAMAYIM_NEKUDOTAYIM:(范围解析操作符)。
- T_OPEN_TAG, T_CLOSE_TAG:
<?php和?>。
查询完整列表:官方手册 List of Parse Tokens
注意事项与局限
-
性能问题:
token_get_all()是一个开销不低的函数,对于大型项目(成千上万的 PHP 文件),最好缓存 token 分析结果,而不是每次都重新解析。 -
只做词法分析,不做语法分析:
token_get_all()只能告诉你代码的“单词”是什么。- 它不能告诉你这些单词组合是否合法(
1 +后面缺少一个数字,它不会报错,它只是输出[T_LNUMBER, '1'], '+')。 - 它无法检测语法错误,如果代码中存在语法错误,
token_get_all()会返回T_UNKNOWN或不完整的结果,但不会抛出异常,要检测错误,应使用php -l或php_check_syntax()(已废弃)或更高级的工具。
-
与 AST(抽象语法树)的关系:
token_get_all()是构建 AST 的基础步骤,如果你只是想检查变量名、函数调用等简单结构,用 token 就够了,但如果需要进行复杂的重构(如改变方法签名、重命名变量),则必须生成 AST(可以使用nikic/php-parser这个 Composer 包)。 -
编码问题:确保输入的
$code字符串的编码是正确的(通常是 UTF-8),否则解析可能会产生意外结果。
| 特性 | 说明 |
|---|---|
| 本质 | PHP 内置的、高效的词法分析器。 |
| 输入 | PHP 源代码字符串。 |
| 输出 | 一个由简单字符和复合 token(ID、文本、行号)组成的数组流。 |
| 核心用途 | 代码分析工具、格式化器、混淆器、自定义语法高亮、DSL 预处理。 |
| 不能做的事 | 不能检测语法错误或理解代码的逻辑结构(这需要语法分析器构建 AST)。 |
| 性能提示 | 对于大型项目,请考虑缓存解析结果。 |
在实际开发中,如果你需要分析 PHP 代码的结构,token_get_all() 是第一步,而它的输出,可以作为更高级语法分析的基础,对于复杂的代码重构或类型推断,建议考虑使用 nikic/php-parser 这样的专业库。