preg_match_all()最稳,正则覆盖变量、字面量、操作符(如==、>=等),括号递归处理,避免strtok()切错多字符运算符。

规则字符串怎么切分成 token 列表
直接用 preg_match_all() 提取变量、字面量和操作符最稳,别信 strtok() ——它对 == 和 >= 这种多字符运算符会切错。正则要覆盖所有可能的 token 类型:/\w+|\d+|'[^']*'|"[^"]*"|==|!=|>=|,注意单双引号字符串要完整捕获,否则后续解析时会丢内容。
常见错误是没跳过空格或注释,导致 $pos 指针偏移错位;还有没处理连续空格,让 token 数组里混入空字符串。建议在 tokenize 后用 array_filter($tokens, 'strlen') 清一遍。
递归下降解析器怎么控制运算符优先级
不能把所有操作符塞进一个 while 循环里判断。必须分层:先写 parseExpression() 处理 ||,再让它调用 parseTerm() 处理 &&,再让 parseTerm() 调用 parseFactor() 处理比较运算符(==、>= 等),最后 parseFactor() 处理变量和字面量。括号靠 parseFactor() 里识别 ( 后递归调用 parseExpression() 解决。
- 漏掉括号递归会导致
(age > 18) && city == 'shanghai'解析成age > (18 && city) == 'shanghai' - 把
&&和||放同一层处理,会违反短路逻辑,执行顺序出错 - 比较运算符没单独一层,
age >= 18 == true这种链式会被错误地当成三元判断
VariableNode::interpret() 怎么安全读取上下文
绝对不要在 interpret() 方法里访问 $GLOBALS 或 $_SERVER。只允许传入一个纯数组上下文,比如 ['user_id' => 123, 'score' => 87],并在 VariableNode 中严格用 isset($context[$this->name]) 查找。查不到时返回 null,而不是抛异常——规则引擎里未定义变量默认参与布尔计算时为 false,比如 undefined_var == 1 应求值为 false。
如果业务需要函数调用(如 in_array(role, ['admin'])),必须显式维护白名单映射表:['in_array' => 'in_array'],且只允许纯函数。禁止映射 system()、file_get_contents() 这类有副作用的函数。
为什么不能直接 eval() 规则字符串
eval() 是最省事的方案,但也是最危险的:规则配置一旦来自用户输入或数据库,就等于开放了任意代码执行入口。哪怕加了 strip_tags() 或正则过滤,也挡不住绕过手段(比如用十六进制编码、Unicode 变体、或拼接字符串)。
解释器模式真正的价值不在“能跑”,而在“可控”:每个节点类型明确、执行路径封闭、变量作用域隔离、函数调用白名单可审计。性能上确实比 eval() 慢,但规则引擎本就不该高频执行复杂表达式;真有性能瓶颈,应该缓存解析后的 AST 节点树,而不是退回到 eval()。
最容易被忽略的一点:解释器必须把整个 token 流消耗完。如果 parseExpression() 返回后 $pos 没走到末尾,说明语法有误(比如多了一个 ) 或少了一个 ||),这时应报错,而不是静默忽略剩余 token —— 否则看似运行成功,实际逻辑已错。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











