
# Értékkonverzió

Minden hívás, amely átlépi a PHP↔JS határt — `setGlobal`, `call`, vagy a JS környezetből meghívott gazdafüggvény — konverteren halad át:

- **PHP → JS**, amikor gazdaoldali értékek kerülnek a motorba (`setGlobal()`, `call()` argumentumai, gazdafüggvények visszatérési értékei).
- **JS → PHP**, amikor JS értékek kerülnek vissza. Két út van, kissé eltérő szabályokkal: a `eval()` és `call()` eredményei, valamint az argumentumok, amelyeket a JS ad át egy gazdafüggvénynek.

A konverzió másolatot készít. A primitívek primitívekké alakulnak, a tömbök és objektumok új struktúrákba másolódnak, az PHP hívható elemek pedig JS függvényként kapnak burkolatot. A hívható elemeken kívül semmi sem osztozik referencián keresztül.

## PHP → JS

| PHP érték | JS eredmény |
|---|---|
| `null` | `null` |
| `true` / `false` | `true` / `false` |
| `int` | `number` (IEEE 754 double; a 2^53 feletti egész számok elveszítik pontosságukat) |
| `float` | `number` (a NaN, ±Infinity és ±0 megmarad) |
| `string` | `string` (az UTF-8 PHP bájtokat UTF-16 kódegységként értelmezi), akkor is, ha PHP függvényt nevez meg |
| list `array` (a 0, 1, 2, … kulcsok sorrendben) | új `Array`, az elemek rekurzív konverziójával |
| bármely más `array` | új, egyszerű `Object`, az értékek rekurzív konverziójával |
| `Closure`, meghívható objektum, például a `[$object, 'method']` hívható tömb | `function`, amely meghívja a PHP hívható elemet |
| bármely más objektum, `Generator`, erőforrás | `undefined` |

A vegyes kulcsú tömbök (a PHP `[0 => 'a', 'name' => 'b']` típusai) egyszerű objektumokká válnak, mivel az JS tömbjeinek indexei csak folytonos egész számok lehetnek.

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

Mivel a tömbök másolatként jönnek létre, a JS módosításai a JS környezetében maradnak:

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

A nem hívható PHP objektumok nem konvertálhatók; `undefined` formájában érkeznek meg ([18. számú issue](https://github.com/inline0/phasis/issues/18)). Alakítsa őket előbb tömbbé, például a `get_object_vars()` segítségével, vagy tegye elérhetővé a metódusaikat closure-ökként. Lásd: [Megosztott objektumok](https://phasis.inline0.dev/hu/docs/interop/shared-objects).

## JS → PHP: `eval()` és `call()` eredmények

| JS érték | PHP eredmény |
|---|---|
| `undefined`, `null` | `null` |
| `boolean` | `bool` |
| `number` (egész, az int-tartományon belül) | `int` (a `-0` `0`-vé válik) |
| `NaN`, `Infinity`, `-Infinity` | `NAN`, `INF`, `-INF` |
| `number` (egyéb) | `float` |
| `string` | `string` (UTF-8) |
| `bigint`, `symbol` | `null` |
| `function` | `null` |
| `Array` | list `array`, az elemek rekurzív konverziójával |
| `TypedArray` | list `array` számokból |
| bármely más objektum (egyszerű objektumok, `Error`, `Date`, `Map`, `Set`, `Promise`, `ArrayBuffer`, …) | asszociatív `array` a saját adat-tulajdonságaiból, a hozzáférőket és függvényértékű tulajdonságokat kihagyva |

Az utolsó sor azt jelenti, hogy a belső slotokban tárolt állapotú objektumok üres tömbbé alakulnak: a `new Date(0)`, a `new Map([["a", 1]])` és a `Promise.resolve(5)` egyaránt `[]`-t ad vissza. Egy `Error` a saját `message` és `stack` értékeit adja vissza. Az önmagára hivatkozó objektum a belső hivatkozásait `null`-vé alakítja.

```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)');              // []
```

Ha ezek közül valamelyik értékre van szüksége PHP-ben, végezze el a konverziót a JS környezetében:

```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: gazdafüggvény argumentumai

A JS által egy PHP gazdafüggvénynek átadott argumentumoknál ugyanazok a szabályok érvényesek a primitívekre, tömbökre és egyszerű objektumokra, a következő eltérésekkel:

| JS érték | PHP argumentum |
|---|---|
| `TypedArray`, `ArrayBuffer`, `DataView` | a motor saját `JsValue` objektuma, konvertálás nélkül |
| object | asszociatív `array` az összes saját tulajdonságával, a hozzáférőket (amelyek getterei lefutnak) és a függvényértékű tulajdonságokat is beleértve |
| arrow function | egy `array` (általában üres) |
| `function` a `function` kulcsszóval deklarálva, vagy bármely önmagára hivatkozó objektum | nincs konverzió: a másolás nem ér véget, és az PHP kifogy a memóriából ([21. számú issue](https://github.com/inline0/phasis/issues/21)) |

Gazdafüggvényben még nem fogadható JS visszahívás. Ezen a határon függvények helyett adatot adjon át.

## Számok és pontosság

A JavaScript számai IEEE 754 double értékek. A PHP intjei a platform natív típusai (minden modern összeállításban 64 bitesek). A konverzió ±2^53-ig pontosan megőrzi az egész számokat; az ennél nagyobb PHP int értékek `number`-vé alakulnak, és 16 tizedesjegy fölött pontatlanná válnak. Ha ennél nagyobb egész számokkal kell pontosan számolnia, használja a `BigInt`-t a JS környezetében, az eredményt pedig karakterláncként adja át.

## Karakterláncok

A PHP karakterláncai bájttömbök; a JS karakterláncai UTF-16 kódegység-sorozatok. A motor úgy teremti meg a kapcsolatot közöttük, hogy a PHP bájtokat UTF-8-ként értelmezi, majd belsőleg UTF-16-ba kódolja újra. Ez érvényes UTF-8 bemenet esetén veszteségmentes. Az érvénytelen UTF-8 bájtok változatlanul kerülnek továbbításra, nem helyettesíti őket U+FFFD karakterrel.

A `String.length`, az indexelt hozzáférés (`s[0]`) és a szeletelés egyaránt UTF-16 kódegységeket számol az ECMAScript-specifikáció szerint — nem kódpontokat és nem bájtokat. Ezért az olyan, kiterjesztett síkbeli karaktert tartalmazó karakterláncok, mint a `'🍕'`, `.length === 2`-t tartalmaznak (az U+1F355 két helyettesítő felét).

## Körfordulós stabilitás

Az egyszerű adatok oda-vissza konvertálás után is megmaradnak: egy skalárokból, listákból és asszociatív tömbökből álló PHP érték, amely a `setGlobal()` felől kerül be, majd a `eval()` vagy `call()` felől tér vissza, ugyanazt a struktúrát adja, kivéve, hogy az egész értékű lebegőpontos számok `int`-ként térnek vissza (a `1.0` `1`-t ad vissza).

A fenti táblázatokban szereplő összes többi, `null`, `undefined` vagy `[]` értékre leképezett típus nem alakítható oda-vissza veszteség nélkül.
