跳转到正文

快速入门

安装

命令行
composer require phasis/phasis

Phasis 需要 PHP 8.2 或更高版本,以及三个扩展;它们都是 composer.json 所必需的:

  • mbstring — 字符串处理。
  • bcmath — BigInt 运算和整数精度数值处理。
  • curl — fetch() 背后的默认传输机制。

mbstring 和 curl 在大多数构建中均已启用。bcmath 通常未启用:官方 php Docker 镜像(php:8.x-cli、php:8.x-fpm)未包含它,有些发行版则将其单独打包(php-bcmath)。缺少它时,Composer 会拒绝安装 Phasis。如果 Dockerfile 基于官方镜像,请添加:

Dockerfile
RUN docker-php-ext-install bcmath

运行 php -m,查看你的构建启用了哪些扩展。

ext-intl 是可选的。Phasis 即使没有它也能运行,但整个 Intl.* 系列(Collator、NumberFormat、DateTimeFormat、PluralRules、Locale、DisplayNames、ListFormat、RelativeTimeFormat、Segmenter、DurationFormat)以及非 ISO 的 Temporal 日历(hebrew、islamic、japanese、persian 等)都需要它。只要宿主应用提供涉及区域设置格式化的 JS,就应安装它。ext-openssl 也是可选的:crypto.subtle 的 AES-CBC、AES-CTR 和 AES-GCM 加密需要它,而摘要和 HMAC 无需它也能运行。

无需 exec,无需 FFI,无需 Node.js,也无需自定义原生扩展。

从 CLI 运行脚本

命令行
./vendor/bin/phasis -e 'console.log(1 + 2 * 3)'
# 7

./vendor/bin/phasis -e 'console.log([1, 2, 3].map(x => x * x).join(","))'
# 1,4,9

./vendor/bin/phasis path/to/script.js

CLI 会输出脚本通过 console.log 写入的内容;它不会输出最后一个表达式的值。

完整命令集请参阅 CLI 命令参考。

在 PHP 中嵌入

PHP
<?php

require __DIR__ . '/vendor/autoload.php';

use Phasis\Engine;

$engine = new Engine();

// Evaluate an expression
$result = $engine->eval('1 + 2 * 3');
echo $result;
// 7

// Execute a script file
$engine->execFile('path/to/script.js');

// Bridge a PHP value into the JS global scope
$engine->setGlobal('config', ['debug' => true, 'version' => '1.0']);
$engine->eval('console.log(config.version)');

// console output is buffered on the engine, not printed
echo $engine->getConsoleOutput();
// 1.0

Engine 实例会在多次调用之间保留其全局状态,因此后续调用 eval() 时可以使用之前定义的内容。

console.log 及其同类方法会将内容追加到引擎的缓冲区,而不是写入 stdout。可通过 getConsoleOutput()(一个字符串,各行以 \n 连接)或 getConsoleLines()(行列表)读取,并通过 clearConsole() 清空。

冷启动开销很低:仅预先安装规范加载所需的核心部分(Object、Function、Array、Promise、Symbol、BigInt、RegExp 和错误类型)。Map、TypedArray、Temporal、Intl、URL、fetch、完整的 Web Platform / Fetch Packs 以及其他所有内容,都会在首次读取时透明地实例化。构造时传入 new Engine(eager: true) 可安装所有内置项——适用于在读取描述符之前检查它们的一致性工具;常规嵌入通常无需此选项。

从 PHP 调用 JS 函数

PHP
$engine->eval('function add(a, b) { return a + b; }');
echo $engine->call('add', 2, 3);
// 5

call() 会按名称在全局对象上查找函数,并使用提供的 PHP 参数调用它(参数会自动转换为 JS 值)。

将可调用的 PHP 暴露为 JS 函数

PHP
$engine->setGlobal('fetchData', function (string $url): array {
    return json_decode(file_get_contents($url), true);
});

$engine->eval('const users = fetchData("https://api.example.com/users")');

PHP 闭包会被包装为 JS 函数。调用时,参数会从 JS 转换为 PHP,返回值则会从 PHP 转换回 JS。

PHP 对象

PHP 对象目前还不能与 JavaScript 共享。将普通 PHP 对象传给 setGlobal() 会绑定 undefined(相关问题见 issue #18)。请将数据作为数组传递;数组会被复制到 JS 中,并将行为作为闭包暴露:

PHP
class Counter {
    public int $value = 0;
    public function increment(): void { $this->value++; }
}

$counter = new Counter();
$engine->setGlobal('increment', fn () => $counter->increment());

$engine->eval('increment(); increment();');

echo $counter->value;
// 2

详情及其他变通方法请参阅 共享对象。

资源限制

PHP
$engine = new Engine();
$engine->setLimit('maxLoopIterations', 100_000);

maxLoopIterations(默认值为 100,000)是唯一可在运行时调整的限制,目前仅适用于树遍历解释器运行的代码。字节码 VM 运行的循环(函数体和大多数顶层代码)不计入限制,因此仅靠这一限制无法阻止 while (true) {}(issue #19)。达到限制时,可在 JS 中捕获错误;若未捕获,错误会以 Phasis\Exceptions\InternalError 的形式传递到 PHP,它是 Phasis\Exceptions\RuntimeError 的子类。

引擎还设有固定的 4,096 帧调用栈上限(转译闭包另有 8,192 帧保护);超过上限会抛出“Maximum call stack size exceeded”,同样属于 InternalError。引擎没有墙钟时间限制,因此请在 PHP 自身的 max_execution_time 中或单独进程里运行不可信代码。参阅 API:setLimit。

后续步骤

  • API 参考 — 完整的 Phasis\Engine 接口。
  • 互操作 — 值转换规则、宿主函数和 PHP 对象。
  • 兼容性 — 已实现的功能和 test262 覆盖率。
  • 解析器和格式化程序 — 解析和格式化 JavaScript,而不执行它。
  • 高级主题 — 架构、字节码 VM、基准测试和预言机测试。