
# 共享对象

目前不支持将 PHP 对象按引用传给 JavaScript，以读取其属性、调用其方法并查看 PHP 中的变化。此问题正在 [问题 #18](https://github.com/inline0/phasis/issues/18) 中跟踪。本页介绍当前的行为以及可用的替代模式。

## 当前行为

`setGlobal()` 会按照 [值转换](https://phasis.inline0.dev/zh/docs/interop/values) 中的规则转换其值。不可调用的 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](https://github.com/inline0/phasis/issues/20)）。参见 [宿主函数](https://phasis.inline0.dev/zh/docs/interop/host-functions#throwing-back-into-js)。
- 使用 `setGlobal()` 注册的闭包会让其捕获的对象保持存活，直到引擎释放该全局对象。
