
# Konverzia hodnôt

Každé volanie cez hranicu PHP↔JS — `setGlobal`, `call` alebo hostiteľská funkcia zavolaná z JS — prechádza konvertorom:

- **PHP → JS**, keď hodnoty hostiteľa vstupujú do enginu (`setGlobal()`, argumenty `call()`, návratové hodnoty hostiteľských funkcií).
- **JS → PHP**, keď hodnoty JS smerujú von. Existujú dve cesty s mierne odlišnými pravidlami: výsledky z `eval()` a `call()` a argumenty, ktoré JS odovzdáva hostiteľskej funkcii.

Konverzia vytvára kópie. Primitívne hodnoty sa mapujú na primitívne hodnoty, polia a objekty sa kopírujú do nových štruktúr a volateľné hodnoty PHP sa obaľujú ako funkcie JS. Nič sa nezdieľa prostredníctvom referencie, okrem samotných volateľných hodnôt.

## PHP → JS

| Hodnota PHP | Výsledok JS |
|---|---|
| `null` | `null` |
| `true` / `false` | `true` / `false` |
| `int` | `number` (dvojitá presnosť IEEE 754; celé čísla nad 2^53 strácajú presnosť) |
| `float` | `number` (NaN, ±Infinity, ±0 sa zachovávajú) |
| `string` | `string` (bajty UTF-8 PHP sa interpretujú ako 16-bitové jednotky kódu UTF-16), aj keď označuje funkciu PHP |
| zoznam `array` (kľúče 0, 1, 2, … v poradí) | nové `Array`, prvky sa konvertujú rekurzívne |
| ľubovoľné iné `array` | nový obyčajný `Object`, hodnoty sa konvertujú rekurzívne |
| `Closure`, volateľný objekt, volateľné pole, napríklad `[$object, 'method']` | `function`, ktorá volá volateľnú hodnotu PHP |
| ľubovoľný iný objekt, `Generator`, zdroj | `undefined` |

Polia so zmiešanými kľúčmi (PHP, `[0 => 'a', 'name' => 'b']`) sa menia na obyčajné objekty, pretože polia v JS musia mať súvislý rozsah celočíselných indexov.

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

Keďže sa polia kopírujú, zmeny v JS zostanú v JS:

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

Objekty PHP, ktoré nemožno volať, sa nekonvertujú; dorazia ako `undefined` ([problém č. 18](https://github.com/inline0/phasis/issues/18)). Najskôr ich preveďte na polia, napríklad pomocou `get_object_vars()`, alebo ich metódy sprístupnite ako uzávery. Pozrite si [Zdieľané objekty](https://phasis.inline0.dev/sk/docs/interop/shared-objects).

## JS → PHP: výsledky `eval()` a `call()`

| Hodnota JS | Výsledok PHP |
|---|---|
| `undefined`, `null` | `null` |
| `boolean` | `bool` |
| `number` (celé číslo v rozsahu int) | `int` (`-0` sa zmení na `0`) |
| `NaN`, `Infinity`, `-Infinity` | `NAN`, `INF`, `-INF` |
| `number` (iné) | `float` |
| `string` | `string` (UTF-8) |
| `bigint`, `symbol` | `null` |
| `function` | `null` |
| `Array` | zoznam `array`, prvky sa konvertujú rekurzívne |
| `TypedArray` | zoznam `array` čísel |
| ľubovoľný iný objekt (obyčajné objekty, `Error`, `Date`, `Map`, `Set`, `Promise`, `ArrayBuffer`, …) | asociatívne `array` jeho vlastných dátových vlastností; prístupové vlastnosti a vlastnosti s funkčnými hodnotami sa vynechajú |

Posledný riadok znamená, že objekty so stavom uloženým v interných slotoch sa konvertujú na prázdne pole: `new Date(0)`, `new Map([["a", 1]])` a `Promise.resolve(5)` vracajú `[]`. Objekt `Error` vracia vlastné `message` a `stack`. Objekt, ktorý odkazuje sám na seba, konvertuje svoje vnútorné odkazy na `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)');              // []
```

Keď potrebujete niektorú z týchto hodnôt v PHP, konvertujte ju priamo v JS:

```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: argumenty hostiteľských funkcií

Argumenty, ktoré JS odovzdáva hostiteľskej funkcii PHP, sa pri primitívnych hodnotách, poliach a obyčajných objektoch riadia rovnakými pravidlami, s týmito rozdielmi:

| Hodnota JS | Argument PHP |
|---|---|
| `TypedArray`, `ArrayBuffer`, `DataView` | vlastný objekt enginu `JsValue`, bez konverzie |
| objekt | asociatívne `array` všetkých jeho vlastných vlastností vrátane prístupových vlastností (ktorých gettery sa spustia) a vlastností s funkčnými hodnotami |
| funkcia so šípkou | `array` (zvyčajne prázdny) |
| `function` deklarovaná pomocou kľúčového slova `function` alebo ľubovoľný objekt odkazujúci sám na seba | bez konverzie: kopírovanie sa nikdy neskončí a PHP minie pamäť ([problém č. 21](https://github.com/inline0/phasis/issues/21)) |

Hostiteľská funkcia zatiaľ nedokáže prijať spätné volanie JS. Cez túto hranicu odovzdávajte dáta, nie funkcie.

## Čísla a presnosť

Čísla JavaScript sú dvojité čísla IEEE 754. Celočíselné hodnoty PHP zodpovedajú platforme (64-bitové v každom modernom zostavení). Konverzia zachová celé čísla až do ±2^53 presne; väčšie celé čísla PHP sa zmenia na `number` a pri viac ako 16 desatinných číslicach strácajú presnosť. Ak potrebujete presne počítať s väčšími celými číslami, použite v JS `BigInt` a výsledok odovzdajte ako reťazec.

## Reťazce

Reťazce PHP sú polia bajtov; reťazce JS sú postupnosti 16-bitových jednotiek kódu UTF-16. Engine ich prepája tak, že bajty PHP interpretuje ako UTF-8 a následne ich interne prekóduje do UTF-16. Pri platnom vstupe UTF-8 sa nestratia žiadne údaje. Bajty, ktoré netvoria platné UTF-8, sa prenášajú bez zmeny namiesto toho, aby sa nahradili znakom U+FFFD.

`String.length`, prístup podľa indexu (`s[0]`) aj výrezy počítajú jednotky kódu UTF-16 podľa špecifikácie ECMAScriptu — nie kódové body ani bajty. Reťazce obsahujúce znaky mimo základnej viacjazyčnej roviny, ako napríklad `'🍕'`, preto majú `.length === 2` (dve náhradné polovice znaku U+1F355).

## Stabilita pri spätnom prevode

Jednoduché dáta sa pri spätnom prevode zachovajú: hodnota PHP pozostávajúca zo skalárov, zoznamov a asociatívnych polí, ktorá vstúpi cez `setGlobal()` a vráti sa cez `eval()` alebo `call()`, vytvorí rovnakú štruktúru, s výnimkou celočíselných čísel s desatinnou časťou, ktoré sa vrátia ako `int` (`1.0` vracia `1`).

Všetko ostatné z vyššie uvedených tabuliek, čo sa mapuje na `null`, `undefined` alebo `[]`, sa pri spätnom prevode nezachová.
