Lewati ke konten

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. 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. 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). Lihat Fungsi host.
  • Closure yang didaftarkan dengan setGlobal() membuat objek yang ditangkapnya tetap hidup selama engine menyimpan global tersebut.