因为不安全、不可控、难调试:eval() 会执行恶意代码,PHP 7.2+ 在部分环境禁用,无法拦截计算过程、加日志或校验变量;语法与逻辑错误难以区分,且每次编译 AST 性能差;手写解释器更安全可控。

为什么不用 eval() 直接执行表达式
因为不安全、不可控、难调试。用户输入的规则字符串一旦含恶意代码(比如 system('rm -rf /')),eval() 会直接执行;PHP 7.2+ 还默认禁用 eval() 在某些 SAPI 下(如 CLI 模式下受限);更关键的是,你没法拦截中间计算过程、加日志、做变量白名单校验。
- 真实场景中,规则常来自运营后台或配置文件,必须隔离执行环境
-
eval()无法区分语法错误和逻辑错误——报错是ParseError还是运行时异常?你根本没法统一处理 - 性能上,每次都要重新编译 AST,而解释器模式可缓存解析后的节点树
手写一个最小可用的解释器:词法分析 + 递归下降
不需要引入完整 Parser Generator(如 Antlr),PHP 原生字符串操作 + 简单状态机就能搞定常见规则表达式,比如:"age > 18 && city == 'beijing' || score >= 90"。
- 先用
strtok()或正则preg_match_all('/\w+|\d+|==|!=|>=| 拆出 token 列表 - 定义基础节点类:
class BinaryOpNode { public $left, $op, $right; }、class LiteralNode { public $value; }、class VariableNode { public $name; } - 核心是
parseExpression()函数:遇到&&就拆左右,优先级低于||,再往下递归进parseTerm()处理比较运算符 - 别忘了跳过空格、处理括号嵌套——没括号时靠运算符优先级驱动,有括号就用递归调用自身
$GLOBALS
- 上下文必须是纯数组,禁止传对象(除非你实现 __get 和 __isset,但易失控)
- 查不到变量时,建议返回
null而非抛异常——规则引擎里“未定义变量”常视为false(比如undefined == 1是 false) - 如果业务需要支持函数调用(如
in_array(role, ['admin','editor'])),得单独维护一个白名单函数映射表,且所有函数必须是纯函数(无副作用、无外部依赖) - 避免在
interpret()中做类型强制转换:PHP 的==会隐式转类型,导致'1' == true为 true,建议统一用严格比较===,并在解析时把数字字面量转成(int)或(float)
容易被忽略的边界问题
不是语法对了就能跑通。线上最常崩在这些地方:
- 字符串字面量没处理引号嵌套:
"he said \"yes\""必须用stripcslashes()或手动解析转义,否则parseStringLiteral()会提前截断 - 浮点数精度:规则里写
price == 99.99,但 PHP 浮点存储可能变成99.99000000000001,建议统一转为整数分单位(如价格存为分)或用bccomp() - 短路求值没实现:
false && heavy_computation()如果不检查左操作数结果就直接算右,性能和副作用都会出问题 - 没有超时控制:复杂嵌套规则可能导致栈溢出或死循环,可在
interpret()里加深度计数器,超过 100 层就 throw new RuntimeException('Expression too deep')
真正难的不是写出能跑的解释器,而是让规则在各种脏数据、非法输入、极端嵌套下不 panic、不泄露信息、不耗尽内存——这些细节决定了它能不能进生产环境。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











