ScriptLite — 一个为 PHP 的沙箱化 ECMAScript 子集解释器(可选 C 扩展)
Source: Dev.to
它的功能
它可以在 PHP 中运行 JavaScript(ES5/ES6 子集)。没有文件系统访问、没有网络、没有 eval、没有 require —— 脚本只能操作你显式传入的数据。可以把它看作一个沙箱,用户编写逻辑,而你完全控制他们能看到和能做的事情。
$engine = new ScriptLite\Engine();
// 存在于数据库中的用户自定义定价规则
$rule = '
let total = items.reduce((sum, item) => sum + item.price * item.qty, 0);
if (total > 100) total *= (1 - discount);
Math.round(total * 100) / 100;
';
$result = $engine->eval($rule, [
"items" => [
["price" => 29.99, "qty" => 2],
["price" => 49.99, "qty" => 1],
],
"discount" => 0.1,
]);
// $result === 98.97它支持人们日常实际使用的特性:箭头函数、解构、模板字面量、展开/剩余参数、数组方法(map、filter、reduce …)、对象方法、正则、try/catch、Math、JSON、Date 等等。
PHP 互操作
你可以直接传入 PHP 对象。脚本可以读取属性、调用方法,且修改会回流到 PHP 端:
$order = new Order(id: 42, status: 'pending');
$engine->eval('
if (order.total() > 500) {
order.applyDiscount(10);
order.setStatus("vip");
}
', ["order" => $order]);
// $order->status 现在是 "vip"你也可以把 PHP 闭包作为可调用函数传入,这样就能精确控制脚本拥有的能力:
$engine->eval('
let users = fetchUsers();
let active = users.filter(u => u.lastLogin > cutoff);
active.map(u => u.email);
', [
"fetchUsers" => fn() => $userRepository->findAll(),
"cutoff" => strtotime("-30 days"),
]);三种执行后端
字节码 VM
编译为字节码,在纯 PHP 的基于栈的 VM 上运行。 兼容性好,无需依赖。
PHP 转译器
将 JavaScript 转换为 PHP 源码,交由 OPcache/JIT 优化。 比 VM 快约 40 倍,适合热点路径。
C 扩展
使用计算跳转的本地字节码 VM。 比 PHP VM 快约 180 倍,出于对极致速度的好奇而实现。
无论使用哪种后端,API 都保持一致。引擎会自动选择最快的可用实现:
$engine = new Engine(); // 如果加载了 C 扩展则使用,否则使用 PHP VM
$engine = new Engine(false); // 强制使用纯 PHP
// 相同代码、相同结果、不同速度
$result = $engine->eval('items.filter(x => x > 3)', ["items" => [1, 2, 3, 4, 5]]);如果想在没有 C 扩展的情况下获得接近原生的速度,转译路径非常有用:
// 转译一次,使用不同数据多次运行
$callback = $engine->getTranspiledCallback($script, ["data", "config"]);
$result1 = $callback(["data" => $batch1, "config" => $cfg]);
$result2 = $callback(["data" => $batch2, "config" => $cfg]);可能的使用场景
- 用户自定义公式 —— 让用户在 CMS、表单构建器或类似电子表格的应用中编写
price * quantity * (1 - discount)。 - 验证规则 —— 存储诸如
value.length > 0 && value.length的规则。
共计 909 条测试,覆盖所有后端。MIT 许可证。支持 PHP 8.3+。
欢迎分享你的想法,尤其是如果你也遇到“需要用户编写逻辑但不能让他们写 PHP”这种情况时的解决方案。你最终是怎么做的?