Phasis
以 PHP 编写的 JavaScript 引擎:通过全部 50,506 test262 测试(2025-05-05 快照),支持 ES2024+,以及 URL、编码、structuredClone、fetch 和 Streams。
composer require phasis/phasis最新版本 v0.4.2 MIT PHP 8.2+, ext-bcmath, ext-curl, ext-mbstring
./vendor/bin/phasis -e '1 + 2 * 3'
# 7
./vendor/bin/phasis -e '[1, 2, 3].map(x => x * x).join(",")'
# 1,4,9
./vendor/bin/phasis path/to/script.js实践应用
一个引擎对象可在 PHP 进程中运行 JavaScript,并双向传递值。
从 PHP 运行 JavaScript
Phasis\Engine 在调用它的 PHP 进程中执行源代码字符串和文件。setGlobal() 会将 PHP 数组转换为 JavaScript 数组和对象,并将 PHP 闭包包装为函数。
console.log 的输出会缓存在引擎中,并通过 getConsoleOutput() 读取。第二个标签页中的格式化器完全不需要引擎。
use Phasis\Engine;
$engine = new Engine();
// Evaluate an expression
$engine->eval('1 + 2 * 3'); // 7
// Run a file
$engine->execFile('/path/to/script.js');
// Bridge a PHP value into JS (arrays are copied)
$engine->setGlobal('config', ['debug' => true]);
$engine->eval('config.debug'); // true
// console.log is buffered, not printed
$engine->eval('console.log("hi")');
echo $engine->getConsoleOutput(); // hi
// Expose a PHP closure as a JS function
$engine->setGlobal('greet', fn(string $name) => "Hello, $name");
$engine->eval('greet("world")'); // "Hello, world"use Phasis\Formatter\Formatter;
use Phasis\Formatter\FormatOptions;
$formatted = Formatter::formatSource(
"const a={x:1,y:2};",
'script',
new FormatOptions(useTabs: true, singleQuote: true),
);
// "const a = { x: 1, y: 2 };\n"纯 PHP
无需 exec()、FFI 或 Node。需要 PHP 8.2+,并启用 ext-curl、ext-bcmath 和 ext-mbstring;CI 使用 PHP 8.4 和 8.5 运行。
100% test262
在 ECMAScript 一致性套件的 2025-05-05 快照中,通过全部 50,506 测试,覆盖每个类别且没有跳过项。
现代 ECMAScript
支持类、async/await、生成器、Symbol、BigInt、Proxy、Temporal、TypedArray 和 Intl(需 ext-intl)。完整支持 ES2024+。
Web 平台 API
内置 URL、TextEncoder 和 TextDecoder、atob 和 btoa、structuredClone、performance 及 DOMException。
fetch + 数据流
通过 ext-curl 从 JS 发出真实的 HTTP 请求。完整支持 Fetch API,包括 AbortSignal、Blob、FormData 和 WHATWG Streams。
Web 平台测试
导入的 WPT fetch、headers、blob、abort、streams 和 encoding 子测试均通过,brotli 和 SharedArrayBuffer 案例的允许列表除外。
PHP ↔ JS 互操作
setGlobal() 将 PHP 闭包公开为 JS 函数,call() 则从 PHP 调用 JS 函数。值会在边界处转换;数组会被复制。
可嵌入
一个 Engine 类:eval、execFile、setGlobal、call。可嵌入任何 PHP 应用或 CLI。
调用栈上限
固定的 4,096 帧调用栈可通过可捕获的 InternalError 阻止失控递归。
CLI
bin/phasis 可运行文件或 -e 代码片段,并通过 --ast 打印 AST。在代码仓库检出目录中,bin/test262 可运行一致性测试套件。
Node.js 基准
场景会与 Node.js(V8)进行比对。触及引擎的推送会在 CI 中运行 WPT 和库测试套件;完整的 test262 测试可按需运行,上次运行于 2026-06-09。
真实库
204 个 npm 软件包(包括 acorn、mustache.js、lodash 和 marked)均经过测试,输出与 Node 逐字节相同。
为什么选择 Phasis
无需 Node.js 进程
Phasis 直接在 PHP 内部对 JavaScript 进行词法分析、解析和执行,无需通过 exec() 调用 Node,也不使用 FFI。它以 Composer 软件包的形式安装。
由宿主控制 fetch
fetch() 默认使用 ext-curl。setFetchTransport() 可替换传输方式,例如在测试中使用模拟对象;setFetchPolicy() 则可在请求发出前拒绝或重写每个请求。
虚拟时间上的计时器
eval() 会在返回前清空事件循环,并让时钟跳到下一个待处理的计时器,而不是休眠。长期运行的宿主可以通过 tickEventLoop() 按实时方式驱动循环。
内置解析器和格式化器
解析器会返回带类型的 PHP AST,并可导出为 ESTree。格式化器会以兼容 prettier 的布局重新输出 JavaScript、JSON 和 CSS。
对比
在 PHP 应用中运行或处理 JavaScript 的其他方式。
| 工具 | 擅长领域 | 与 Phasis 的区别 |
|---|---|---|
| Node.js | 擅长领域Node.js 在 V8 引擎上运行 JavaScript,并从 node_modules 文件夹解析软件包,因此 npm 依赖项可按名称加载。 | 与 Phasis 的区别从 PHP 调用 Node 会增加第二个运行时和子进程边界。Phasis 在 PHP 内运行,但它的模块加载器不支持 node_modules,也不支持解析 package.json 导出,因此必须先打包 npm 软件包,例如使用 esbuild。 |
| V8Js | 擅长领域V8Js 将 Google 的 V8 引擎嵌入 PHP,并在沙箱中运行 JavaScript,可通过时间限制和内存限制来约束沙箱。 | 与 Phasis 的区别V8Js 是需要 libv8 库的已编译扩展。Phasis 是来自 Composer 的纯 PHP,其文档称,在受调用分派限制的代码中,运行时约为 100,是 V8 的倍数。 |
| Peast | 擅长领域Peast 可在 PHP 5.4 及更高版本中将 JavaScript 解析为 ESTree AST,并支持树遍历、选择器查询和渲染。 | 与 Phasis 的区别Phasis 需要 PHP 8.2。它的解析器返回带类型的只读节点对象,并可导出为 ESTree;同一个软件包也可以执行代码。 |
| Prettier | 擅长领域Prettier 可格式化 JavaScript、TypeScript、JSX、CSS、HTML、Markdown、YAML 等,并可从 npm 安装。 | 与 Phasis 的区别Phasis 格式化器仅支持 JavaScript、JSON 和 CSS。其选项与 prettier 3.x 一致,并在 PHP 中运行,无需 Node.js。 |
问答
来自 Phasis 文档的简明解答。
Composer 需要 PHP 8.2 或更高版本,并启用 bcmath、curl 和 mbstring。Intl API 和非 ISO Temporal 日历建议启用 intl;crypto.subtle 中的 AES 加密则需要 openssl。
Phasis 是采用字节码 VM 的树遍历解释器,而不是 JIT。文档称,在受调用分派限制的代码中,其运行时间约为 100,是 V8 的倍数;他们表示,这个上限对于验证规则等嵌入式工作通常影响不大。
不能按名称导入。模块加载器根据文件系统解析导入,需要显式的文件扩展名,并且不支持 node_modules 或 package.json 导出;因此,请使用 esbuild 等工具打包软件包,并将打包结果传给 Phasis。
没有。不会提供 window、document 或 DOM 树,也不会提供 process、Buffer 和 require() 等 Node 全局变量。
输出会缓存在引擎中,而不是直接打印。可通过 getConsoleOutput() 或 getConsoleLines() 读取,并通过 clearConsole() 清空缓冲区。
未捕获的抛出会显示为 Phasis\Exceptions\JsThrowable。引擎自身引发的错误(例如读取 null 的属性)会显示为 Phasis\Exceptions\TypeError、ReferenceError 或 RangeError,它们都继承自 Phasis\Exceptions\RuntimeError。无效源码会抛出 Phasis\Parser\ParseError,即一个 Phasis\Exceptions\SyntaxError,其中包含行号、列号和偏移量。
先从入门指南开始,然后阅读 API 参考文档,了解 fetch 钩子和事件循环。