
# Pretvorba vrednosti

Vsak klic, ki prečka mejo PHP↔JS — `setGlobal`, `call` ali gostiteljska funkcija, priklicana iz JS — gre skozi pretvornik:

- **PHP → JS**, ko vrednosti gostitelja prehajajo v mehanizem (`setGlobal()`, argumenti `call()`, vrnjene vrednosti gostiteljskih funkcij).
- **JS → PHP**, ko vrednosti JS prehajajo nazaj. Obstajata dve poti z nekoliko različnimi pravili: rezultati `eval()` in `call()` ter argumenti, ki jih JS posreduje gostiteljski funkciji.

Pretvorba kopira. Primitivi se preslikajo v primitive, polja in objekti se kopirajo v nove strukture, funkcije PHP pa se ovijejo kot funkcije JS. Vse se kopira, razen samih funkcij, ki ostanejo deljene prek sklica.

## PHP → JS

| Vrednost PHP | Rezultat JS |
|---|---|
| `null` | `null` |
| `true` / `false` | `true` / `false` |
| `int` | `number` (dvojna natančnost IEEE 754; cela števila nad 2^53 izgubijo natančnost) |
| `float` | `number` (NaN, ±Infinity, ±0 se ohranijo) |
| `string` | `string` (bajti PHP UTF-8 se interpretirajo kot 16-bitne kodne enote UTF-16), tudi če poimenujejo funkcijo PHP |
| seznam `array` (ključi 0, 1, 2, … po vrsti) | novo `Array`, elementi se pretvorijo rekurzivno |
| katerikoli drug `array` | nov navaden `Object`, vrednosti se pretvorijo rekurzivno |
| `Closure`, objekt, ki ga je mogoče priklicati, funkcijsko polje, kot je `[$object, 'method']` | `function`, ki kliče funkcijo PHP |
| katerikoli drug objekt, `Generator`, vir | `undefined` |

Polja z mešanimi ključi (PHP: `[0 => 'a', 'name' => 'b']`) postanejo navadni objekti, ker morajo imeti polja v JS zvezno območje celih števil kot indeksov.

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

Ker se polja kopirajo, spremembe v JS ostanejo v JS:

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

Objekti PHP, ki jih ni mogoče priklicati, se ne pretvorijo; prispejo kot `undefined` ([težava #18](https://github.com/inline0/phasis/issues/18)). Najprej jih pretvorite v polja, na primer z `get_object_vars()`, ali njihove metode izpostavite kot zaprtja. Glejte [Deljeni objekti](https://phasis.inline0.dev/sl/docs/interop/shared-objects).

## JS → PHP: rezultati `eval()` in `call()`

| Vrednost JS | Rezultat PHP |
|---|---|
| `undefined`, `null` | `null` |
| `boolean` | `bool` |
| `number` (celo število, znotraj območja int) | `int` (`-0` postane `0`) |
| `NaN`, `Infinity`, `-Infinity` | `NAN`, `INF`, `-INF` |
| `number` (drugo) | `float` |
| `string` | `string` (UTF-8) |
| `bigint`, `symbol` | `null` |
| `function` | `null` |
| `Array` | seznam `array`, elementi se pretvorijo rekurzivno |
| `TypedArray` | seznam `array` števil |
| katerikoli drug objekt (navadni objekti, `Error`, `Date`, `Map`, `Set`, `Promise`, `ArrayBuffer`, …) | asociativno `array` njegovih lastnih podatkovnih lastnosti; lastnosti z dostopniki in funkcijskimi vrednostmi se preskočijo |

Zadnja vrstica pomeni, da se objekti, katerih stanje je shranjeno v notranjih režah, pretvorijo v prazno polje: `new Date(0)`, `new Map([["a", 1]])` in `Promise.resolve(5)` vsi vrnejo `[]`. `Error` vrne svoj lastni `message` in `stack`. Objekt, ki se sklicuje sam nase, pretvori svoje notranje sklice v `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)');              // []
```

Vrednost pretvorite znotraj JS, kadar jo potrebujete v 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: argumenti gostiteljskih funkcij

Argumenti, ki jih JS posreduje gostiteljski funkciji PHP, sledijo istim pravilom za primitive, polja in navadne objekte, z naslednjimi razlikami:

| Vrednost JS | Argument PHP |
|---|---|
| `TypedArray`, `ArrayBuffer`, `DataView` | lasten objekt `JsValue` mehanizma, brez pretvorbe |
| objekt | asociativno `array` vseh njegovih lastnih lastnosti, vključno z dostopniki (katerih pridobivalniki se izvedejo) in lastnostmi s funkcijskimi vrednostmi |
| puščična funkcija | `array` (običajno prazna) |
| `function`, deklariran s ključno besedo `function`, ali katerikoli objekt, ki se sklicuje sam nase | brez pretvorbe: kopiranje se nikoli ne konča in PHP zmanjka pomnilnika ([težava #21](https://github.com/inline0/phasis/issues/21)) |

Za zdaj ni mogoče prejeti povratnega klica JS v gostiteljski funkciji. Čez to mejo prenašajte podatke, ne funkcij.

## Števila in natančnost

Števila JavaScript so dvojne natančnosti IEEE 754. Celi števili PHP sta izvorni za platformo (64-bitni v vsaki sodobni gradnji). Pretvorba natančno ohrani cela števila do ±2^53; večja cela števila PHP postanejo `number` in izgubijo natančnost po 16 decimalnih mestih. Če potrebujete natančno aritmetiko s celimi števili, večjimi od te meje, uporabite `BigInt` znotraj JS, rezultat pa posredujte kot niz.

## Nizi

Nizi PHP so polja bajtov; nizi JS so zaporedja 16-bitnih kodnih enot UTF-16. Mehanizem jih poveže tako, da bajte PHP interpretira kot UTF-8 in jih interno znova kodira v UTF-16. To je brezizgubno za veljaven vhod UTF-8. Bajti, ki niso veljaven UTF-8, se posredujejo nespremenjeni, namesto da bi jih nadomestili z U+FFFD.

`String.length`, indeksirani dostop (`s[0]`) in rezanje niza štejejo 16-bitne kodne enote UTF-16, kot določa specifikacija ECMAScript — ne kodnih točk ali bajtov. Nizi z znaki izven osnovne večjezične ravnine, kot je `'🍕'`, imajo zato `.length === 2` (dve nadomestni polovici znaka U+1F355).

## Stabilnost povratnega kroga

Navadni podatki preživijo povratni krog: vrednost PHP iz skalarjev, seznamov in asociativnih polj, ki vstopi prek `setGlobal()` in se vrne prek `eval()` ali `call()`, ustvari enako strukturo, razen da se celoštevilska števila s plavajočo vejico vrnejo kot `int` (`1.0` vrne `1`).

Vse drugo v zgornjih tabelah, kar se preslika v `null`, `undefined` ali `[]`, se ne ohrani pri povratnem krogu.
