解释器模式通过自定义语法解析和上下文隔离实现安全可控的动态规则执行,避免eval()的安全风险与调试难题。

为什么不能直接用 eval() 做动态过滤
因为不安全、不可控、难调试:eval() 会执行恶意代码,PHP 7.2+ 在部分环境禁用,无法拦截计算过程、加日志或校验变量;语法与逻辑错误难以区分,且每次编译 AST 性能差。用户输入的规则字符串一旦含恶意代码(比如 system('rm -rf /')),eval() 会直接执行;更关键的是,你没法拦截中间计算过程、加日志、做变量白名单校验。
如何用解释器模式解析类似 "age > 18 && city == 'beijing' || score >= 90" 的规则
核心是手写一个轻量状态机 + 递归下降解析器,不依赖 Antlr 等重型工具。关键步骤包括:
- 先用正则或字符串切分把原始表达式转成 token 列表,例如
['age', '>', '18', '&&', 'city', '==', "'beijing'", '||', 'score', '>=', '90'] - 实现
parseExpression():遇到||拆左右,优先级最低;再进parseTerm()处理&&;最后进parseFactor()处理比较运算符(==、>=等)和字面量 - 每个节点(如
AndNode、GreaterThanNode、VariableNode)都实现interpret($context)方法,只从传入的上下文数组读值,不碰全局变量
变量绑定和上下文隔离怎么做
解释器执行时不能直接读全局变量或 $GLOBALS,否则规则能任意访问敏感数据。必须显式传入一个只读上下文数组,并在 VariableNode::interpret($context) 中查表。示例:
$context = [
'age' => 25,
'city' => 'beijing',
'score' => 87
];
$result = $astRoot->interpret($context); // true 或 false
注意:$context 必须提前做过 filter_var_array() 校验,比如用 FILTER_VALIDATE_INT 和 FILTER_SANITIZE_STRING 分别处理数值型和字符串型字段,避免脏数据穿透到解释器内部。
实际部署时最容易被忽略的点
不是语法解析难,而是规则生命周期管理容易出问题:
- 缓存解析后的 AST 节点树,避免每次请求都重新 tokenize + parse;但要注意规则字符串变更后缓存失效逻辑
- 不允许规则中出现函数调用(如
now()、md5()),否则又退化成eval()风险区;所有时间、哈希等能力必须由宿主预先计算好,通过$context注入 - 错误提示要明确区分:是 token 错误(如未闭合引号)、语法错误(如
age && city缺少比较符),还是运行时变量缺失($context里没有age)——这三类错误的堆栈和日志级别应完全不同
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











