
# Objek bersama

Meneruskan objek PHP ke JavaScript melalui referensi — membaca propertinya, memanggil metodenya, dan melihat perubahan di PHP — **belum didukung**. Masalah ini dilacak di [masalah #18](https://github.com/inline0/phasis/issues/18). Halaman ini menjelaskan apa yang terjadi saat ini dan pola alternatif yang dapat digunakan.

## Yang terjadi saat ini

`setGlobal()` mengonversi nilainya menggunakan aturan di [Konversi nilai](https://phasis.inline0.dev/id/docs/interop/values). Objek PHP yang bukan callable tidak memiliki konversi, sehingga global tersebut bernilai `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')
```

Hal yang sama berlaku untuk `stdClass`, entitas dan model, kontainer layanan, generator, serta resource. Ada dua pengecualian untuk objek callable:

- Objek dengan `__invoke()` menjadi fungsi JS yang memanggilnya.
- Array callable seperti `[$counter, 'increment']` menjadi fungsi JS yang memanggil metode tersebut.

Tidak ada `PhpBridge` atau kelas wrapper lain untuk mengekspos objek; `src/Interop/` hanya berisi konverter nilai.

## Ekspos metode sebagai closure

Closure dapat menangkap objek, sehingga JS memanggil fungsi dan metode dijalankan pada instance PHP asli:

```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
```

Array callable melakukan hal yang sama tanpa closure:

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

## Kelompokkan dalam array

Array asosiatif menjadi objek JS, dan closure di dalamnya menjadi fungsi. Dengan begitu, JS memperoleh API berbentuk objek yang metodenya bekerja pada instance 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
```

Hanya hal yang Anda masukkan ke dalam array yang terlihat oleh JS, sehingga status privat seperti `$secret` tetap berada di PHP.

Field data adalah salinan yang diambil saat `setGlobal()` berjalan. Dalam contoh ini, `user.name` berupa string di JS; mengubahnya akan mengubah salinan JS, bukan `$user->name`. Arahkan semua penulisan melalui closure.

## Kirim data keluar, kembalikan hasil

Untuk objek berbentuk data, ubah menjadi array saat masuk, lalu baca hasilnya saat keluar:

```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)` atau metode `toArray()` memberi Anda array untuk sebuah objek. Lalu terapkan array yang dikembalikan ke objek, entitas, atau model Anda di PHP.

## Catatan penting

- Exception yang dilempar di dalam closure ini saat ini tidak dapat ditangkap oleh JS `try` / `catch`; exception tersebut mencapai pemanggil `eval()` ([masalah #20](https://github.com/inline0/phasis/issues/20)). Lihat [Fungsi host](https://phasis.inline0.dev/id/docs/interop/host-functions#throwing-back-into-js).
- Closure yang didaftarkan dengan `setGlobal()` membuat objek yang ditangkapnya tetap hidup selama engine menyimpan global tersebut.
