
# Konversi nilai

Setiap pemanggilan yang melintasi batas PHP↔JS — `setGlobal`, `call`, fungsi host yang dipanggil dari JS — melewati konverter:

- **PHP → JS** saat nilai host masuk ke engine (`setGlobal()`, argumen `call()`, nilai kembalian fungsi host).
- **JS → PHP** saat nilai JS keluar. Ada dua jalur dengan aturan yang sedikit berbeda: hasil dari `eval()` dan `call()`, serta argumen yang diberikan JS ke fungsi host.

Konversi menyalin data. Primitif dipetakan ke primitif, array dan objek disalin ke struktur baru, dan callable PHP dibungkus sebagai fungsi JS. Tidak ada yang digunakan bersama melalui referensi selain callable itu sendiri.

## PHP → JS

| Nilai PHP | Hasil JS |
|---|---|
| `null` | `null` |
| `true` / `false` | `true` / `false` |
| `int` | `number` (double IEEE 754; int di atas 2^53 kehilangan presisi) |
| `float` | `number` (NaN, ±Infinity, ±0 dipertahankan) |
| `string` | `string` (byte PHP UTF-8 ditafsirkan sebagai unit kode UTF-16), termasuk jika namanya sama dengan fungsi PHP |
| daftar `array` (kunci 0, 1, 2, … secara berurutan) | `Array` baru, elemennya dikonversi secara rekursif |
| `array` lainnya | `Object` biasa yang baru, nilainya dikonversi secara rekursif |
| `Closure`, objek yang dapat dipanggil, array callable seperti `[$object, 'method']` | `function` yang memanggil callable PHP |
| objek lainnya, `Generator`, resource | `undefined` |

Array dengan kunci campuran (PHP milik `[0 => 'a', 'name' => 'b']`) menjadi objek biasa karena array di JS harus memiliki indeks integer yang berurutan tanpa celah.

```php
$engine->setGlobal('list', [1, 2, 3]);                 // → JS Array
$engine->setGlobal('map',  ['a' => 1, 'b' => 2]);      // → JS Object
$engine->setGlobal('mixed', [0 => 'x', 'k' => 'v']);   // → JS Object
```

Karena array disalin, perubahan yang dibuat JS tetap berada di JS:

```php
$list = [1, 2, 3];
$engine->setGlobal('list', $list);
$engine->eval('list.push(4)');
count($list);                 // 3
$engine->eval('list.length'); // 4
```

Objek PHP yang bukan callable tidak dapat dikonversi; objek tersebut diterima sebagai `undefined` ([masalah #18](https://github.com/inline0/phasis/issues/18)). Konversikan terlebih dahulu menjadi array, misalnya dengan `get_object_vars()`, atau ekspos metodenya sebagai closure. Lihat [Objek bersama](https://phasis.inline0.dev/id/docs/interop/shared-objects).

## JS → PHP: hasil `eval()` dan `call()`

| Nilai JS | Hasil PHP |
|---|---|
| `undefined`, `null` | `null` |
| `boolean` | `bool` |
| `number` (bilangan bulat, dalam rentang int) | `int` (`-0` menjadi `0`) |
| `NaN`, `Infinity`, `-Infinity` | `NAN`, `INF`, `-INF` |
| `number` (lainnya) | `float` |
| `string` | `string` (UTF-8) |
| `bigint`, `symbol` | `null` |
| `function` | `null` |
| `Array` | daftar `array`, elemennya dikonversi secara rekursif |
| `TypedArray` | daftar `array` berisi angka |
| objek lainnya (objek biasa, `Error`, `Date`, `Map`, `Set`, `Promise`, `ArrayBuffer`, …) | `array` asosiatif berisi properti datanya sendiri, accessor dan properti bernilai fungsi dilewati |

Baris terakhir berarti objek yang statusnya berada di slot internal dikonversi menjadi array kosong: `new Date(0)`, `new Map([["a", 1]])`, dan `Promise.resolve(5)` semuanya mengembalikan `[]`. `Error` mengembalikan `message` dan `stack` miliknya sendiri. Objek yang mereferensikan dirinya sendiri mengonversi referensi internalnya menjadi `null`.

```php
$engine->eval('[1, 2, 3]');                // [1, 2, 3]
$engine->eval('({a: 1, b: 2})');           // ['a' => 1, 'b' => 2]
$engine->eval('new Uint8Array([1, 2, 3])'); // [1, 2, 3]
$engine->eval('1234567890123456789n');     // null
$engine->eval('new Date(0)');              // []
```

Lakukan konversi di dalam JS jika Anda memerlukan salah satu nilai tersebut di PHP:

```php
$engine->eval('(1234567890123456789n).toString()');  // "1234567890123456789"
$engine->eval('new Date(0).toISOString()');           // "1970-01-01T00:00:00.000Z"
$engine->eval('Object.fromEntries(new Map([["a", 1]]))'); // ['a' => 1]
```

## JS → PHP: argumen fungsi host

Argumen yang diberikan JS ke fungsi host PHP mengikuti aturan yang sama untuk primitif, array, dan objek biasa, dengan perbedaan berikut:

| Nilai JS | Argumen PHP |
|---|---|
| `TypedArray`, `ArrayBuffer`, `DataView` | objek `JsValue` bawaan engine, tanpa konversi |
| objek | `array` asosiatif berisi semua propertinya sendiri, termasuk accessor (getter-nya dijalankan) dan properti bernilai fungsi |
| fungsi panah | sebuah `array` (biasanya kosong) |
| `function` yang dideklarasikan dengan kata kunci `function`, atau objek apa pun yang mereferensikan dirinya sendiri | tanpa konversi: penyalinan tidak pernah berakhir dan PHP kehabisan memori ([masalah #21](https://github.com/inline0/phasis/issues/21)) |

Belum ada cara untuk menerima callback JS dalam fungsi host. Kirim data, bukan fungsi, melalui batas ini.

## Angka dan presisi

Angka JavaScript adalah double IEEE 754. Integer PHP menggunakan representasi asli platform (64-bit di semua build modern). Konversi mempertahankan integer hingga ±2^53 secara tepat; integer PHP yang lebih besar menjadi `number` dan kehilangan presisi setelah 16 digit desimal. Gunakan `BigInt` di dalam JS jika Anda memerlukan aritmetika presisi untuk integer yang lebih besar, lalu teruskan hasilnya sebagai string.

## String

String PHP adalah array byte; string JS adalah rangkaian unit kode UTF-16. Engine menjembatani keduanya dengan menafsirkan byte PHP sebagai UTF-8 dan mengodekannya ulang ke UTF-16 secara internal. Proses ini tidak menghilangkan data jika input UTF-8 valid. Byte yang bukan UTF-8 valid diteruskan tanpa perubahan, bukan diganti dengan U+FFFD.

`String.length`, akses terindeks (`s[0]`), dan pemotongan semuanya menghitung unit kode UTF-16 sesuai spesifikasi ECMAScript — bukan titik kode atau byte. Karena itu, string yang berisi karakter astral seperti `'🍕'` memiliki `.length === 2` (dua bagian surrogate dari U+1F355).

## Stabilitas bolak-balik

Data biasa tetap sama setelah bolak-balik: nilai PHP yang terdiri dari skalar, daftar, dan array asosiatif, yang masuk melalui `setGlobal()` lalu kembali melalui `eval()` atau `call()`, menghasilkan struktur yang sama, kecuali float integral yang kembali sebagai `int` (`1.0` mengembalikan `1`).

Selain itu, nilai dalam tabel di atas yang dipetakan ke `null`, `undefined` atau `[]` tidak akan kembali seperti semula.
