token_get_all() 返回混合数组:标量token为字符串,语法类token为[type, value, line]三元组;须用is_array()判别类型,正则无法处理PHP嵌套语法。

PHP 的 tokenizer 扩展不是用来“运行”代码的,而是用来“看清”代码结构的——它把一段 PHP 源码切分成最小可识别单元(token),每个 token 带有类型、值、行号等元信息。这对源码分析和安全重构是不可替代的基础能力。
token_get_all() 返回的数组结构到底长什么样?
很多人一上来就 foreach 循环,却没注意返回值混合了两种格式:
- 标量 token(如
;、{)直接是字符串,不是数组 - 语法类 token(如关键字、字符串字面量、变量名)是三元素数组:
[token_id, "value", line_number](PHP 8.0+ 默认带行号,旧版需手动补)
错误写法:echo $token[1]; —— 遇到 ; 就 Warning: Illegal string offset
正确判断方式:if (is_array($token)) { /* 处理 T_ECHO 等 */ } else { /* 处理纯符号 */ }
立即学习“PHP免费学习笔记(深入)”;
为什么用 token_get_all() 而不用正则匹配 PHP 代码?
正则在 PHP 这种嵌套、引号嵌套、注释干扰严重的语法里必然失败。比如这段合法代码:
echo "abc{$arr['key']}", 'def' . <<<EOD
line {$x}
EOD;用正则提取所有字符串?基本不可能不漏不误。而 token_get_all() 是 PHP 内核词法分析器的输出,天然支持:
- Heredoc/Nowdoc 解析
- 变量插值识别(
$arr['key']在字符串内仍被拆成独立 token) - 注释与代码分离(
T_COMMENT和T_DOC_COMMENT类型明确) - 命名空间、属性、箭头函数等 PHP 7+/8+ 新语法
重构时怎么安全定位并替换某个函数调用?
不能只靠 $token[1] === 'foo',因为变量名、类名、常量也可能撞名。必须结合上下文判断是否为「函数调用」:
- 找到
T_STRING类型且值为'foo'的 token - 检查它后一个 token 是否为
T_OPEN_BRACKET(即() - 再往前看:前一个 token 不能是
T_DOUBLE_COLON(排除Class::foo())、不能是T_OBJECT_OPERATOR(排除$obj->foo())、不能是T_NEW(排除new foo())
简化的判断逻辑:if (is_array($token) && $token[0] === T_STRING && $token[1] === 'foo' && isset($tokens[$i+1]) && is_array($tokens[$i+1]) && $tokens[$i+1][0] === T_OPEN_BRACKET)
行号缺失或错位?别怪 tokenizer,先查 PHP 版本和换行符
PHP 7.4 之前 token_get_all() 不返回行号;PHP 8.0+ 默认返回,但前提是源码用 \n 换行。Windows 的 \r\n 或混合换行会导致 zend_lineno 计数偏移。
补救方法(兼容旧版):$tokens = token_get_all($code); $lines = explode("\n", $code); 然后手动按 \n 数位置——但这只是权宜之计。真正可靠的方案是升级到 PHP 8+ 并统一用 LF 换行。
另一个坑:T_INLINE_HTML token 的行号往往不准,因为它包裹的是非 PHP 区域,解析器只粗略跳过。处理模板混写时要特别留意。



















