跳转到正文

共享对象

目前不支持将 PHP 对象按引用传给 JavaScript,以读取其属性、调用其方法并查看 PHP 中的变化。此问题正在 问题 #18 中跟踪。本页介绍当前的行为以及可用的替代模式。

当前行为

setGlobal() 会按照 值转换 中的规则转换其值。不可调用的 PHP 对象没有对应的转换,因此该全局值为 undefined:

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

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

$engine->eval('typeof counter');
// "undefined"

$engine->eval('counter.increment()');
// throws Phasis\Exceptions\TypeError:
// Cannot read properties of undefined (reading 'increment')

stdClass、实体和模型、服务容器、生成器及资源也同样如此。有两种例外是可调用对象:

  • 带有 __invoke() 的对象会变成调用该对象的 JS 函数。
  • 可调用数组(例如 [$counter, 'increment'])会变成调用该方法的 JS 函数。

没有 PhpBridge 或其他用于暴露对象的包装类;src/Interop/ 只包含值转换器。

将方法暴露为闭包

闭包可以捕获对象,因此 JS 调用函数时,方法会在原始的 PHP 实例上运行:

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

$engine->eval('increment(); increment(); count()');
// 2

echo $counter->value;
// 2

可调用数组无需闭包,也能实现相同效果:

PHP
$engine->setGlobal('increment', [$counter, 'increment']);

将它们分组到数组中

关联数组会变成 JS 对象,其中的闭包会变成函数。这样就为 JS 提供了形似对象的 API,其方法会作用于 PHP 实例:

PHP
class User {
    public function __construct(
        public string $name,
        public int $age,
        private string $secret,
    ) {}

    public function greet(): string {
        return "Hi, I'm $this->name";
    }
}

$user = new User('Ada', 36, 'topsecret');

$engine->setGlobal('user', [
    'name'   => $user->name,
    'greet'  => fn () => $user->greet(),
    'setAge' => function (int $age) use ($user): void { $user->age = $age; },
]);

$engine->eval('user.greet()');
// "Hi, I'm Ada"

$engine->eval('user.setAge(37)');
echo $user->age;
// 37

只有放入数组中的内容才会对 JS 可见,因此 $secret 等私有状态仍保留在 PHP 中。

数据字段会在 setGlobal() 运行时复制。在示例中,user.name 是 JS 中的字符串;对它赋值只会更改 JS 副本,不会更改 $user->name。请通过闭包完成所有写入操作。

传出数据,再传回结果

对于数据形状的对象,在传入时将其转换为数组,并在取回结果时读取:

PHP
$post = ['title' => 'Draft', 'tags' => ['php']];

$engine->setGlobal('post', $post);
$engine->eval('function edit(p) { p.title = "Edited from JS"; return p; }');

$post = $engine->call('edit', $post);
echo $post['title'];
// Edited from JS

get_object_vars($object) 或 toArray() 方法可为对象返回数组。然后在 PHP 中将返回的数组应用到对象、实体或模型上。

注意事项

  • 这些闭包内部抛出的异常目前无法被 JS try / catch 捕获;它们会传递到 eval() 的调用方(问题 #20)。参见 宿主函数。
  • 使用 setGlobal() 注册的闭包会让其捕获的对象保持存活,直到引擎释放该全局对象。