
# 共有オブジェクト

PHPオブジェクトを参照渡しでJavaScriptに渡し、プロパティの読み取り、メソッドの呼び出し、PHP内の変更の確認を行うことは、**まだサポートされていません**。[issue #18](https://github.com/inline0/phasis/issues/18)で追跡されています。このページでは現在の動作と、代わりに使えるパターンを説明します。

## 現在の動作

`setGlobal()`は[値の変換](https://phasis.inline0.dev/ja/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`、エンティティやモデル、サービスコンテナ、ジェネレーター、リソースも同様です。呼び出し可能オブジェクトには次の2つの例外があります：

- `__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オブジェクトになり、その中のクロージャは関数になります。これにより、メソッドがPHPインスタンスに対して動作する、オブジェクト形式のAPIをJSに渡せます：

```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()`の呼び出し元に届きます（[issue #20](https://github.com/inline0/phasis/issues/20)）。[ホスト関数](https://phasis.inline0.dev/ja/docs/interop/host-functions#throwing-back-into-js)を参照してください。
- `setGlobal()`で登録されたクロージャは、エンジンがグローバルを保持する間、キャプチャしたオブジェクトを存続させます。
