
本文详解 `token_get_all()` 返回值结构,指出原代码中 `reset($tokens[1])` 的类型不安全问题,并提供清晰、phpstan 友好的等价展开写法,确保类型推断准确且逻辑不变。
原始代码意图明确:通过将待测字符串包裹为伪 PHP 代码(如 ),调用 token_get_all() 进行词法分析,再判断其首个实际 token(跳过 T_OPEN_TAG)是否为 T_STRING —— 若不是,则说明该字符串本身是 PHP 保留关键字(如 if、class、function 等),因为它们会被识别为对应关键字 token(如 T_IF、T_CLASS),而非普通标识符 T_STRING。
但问题出在这一行:
return reset($tokens[1]) !== T_STRING;
token_get_all() 的返回值类型是 array
更关键的是:reset($tokens[1]) 逻辑本身就有歧义。若 $tokens[1] 是数组(如 [T_IF, 'if', 1]),reset() 返回其第一个元素(即 T_IF);但若它是字符串(如 ';'),reset() 将失败。而原逻辑真正想检查的,是第一个有意义的 token(非 T_OPEN_TAG)的类型 ID,这应直接取 $tokens[1][0](当它是数组时)。
立即学习“PHP免费学习笔记(深入)”;
✅ 推荐的安全展开写法如下:
$tokens = token_get_all('');
// 跳过 T_OPEN_TAG(通常为 $tokens[0]),定位第一个实际 token
$firstRealToken = null;
foreach ($tokens as $token) {
if (is_array($token) && $token[0] === T_OPEN_TAG) {
continue;
}
$firstRealToken = $token;
break;
}
if ($firstRealToken === null) {
return true; // 无有效 token,视为非关键字(边缘情况)
}
if (!is_array($firstRealToken)) {
// 纯字符串 token(如 ';'、'{'),说明原字符串未被解析为关键字或标识符
// 通常意味着输入非法或为空,保守返回 false(非保留字)
return false;
}
// 此时 $firstRealToken 形如 [T_IF, 'if', 1],检查 token ID
return $firstRealToken[0] !== T_STRING;? 注意事项:
- 不要依赖 $tokens[1] 的固定索引:token_get_all() 在遇到空格、注释等时可能插入额外 token,索引不可靠;
- 始终先 is_array() 检查再访问 $token[0],避免类型错误与运行时警告;
- 使用 T_OPEN_TAG 显式跳过起始标签,比硬编码索引更健壮;
- PHPStan 友好:所有分支类型明确,无混合类型操作。
该写法不仅消除了 PHPStan 报错,还提升了可读性与鲁棒性,是生产环境中推荐的实现方式。











