
# Převod hodnot

Každé volání překračující hranici PHP↔JS — `setGlobal`, `call` nebo vyvolání hostitelské funkce z JS — prochází převaděčem:

- **PHP → JS**, když hodnoty hostitelského prostředí vstupují do enginu (`setGlobal()`, argumenty `call()`, návratové hodnoty hostitelských funkcí).
- **JS → PHP**, když hodnoty JS putují zpět ven. Existují dvě cesty s mírně odlišnými pravidly: výsledky `eval()` a `call()` a argumenty, které JS předává hostitelské funkci.

Převod vytváří kopie. Primitivní hodnoty se mapují na primitivní hodnoty, pole a objekty se kopírují do nových struktur a volatelné objekty PHP se obalí jako funkce JS. Nic se nesdílí odkazem, kromě samotných volatelných objektů.

## PHP → JS

| Hodnota PHP | Výsledek JS |
|---|---|
| `null` | `null` |
| `true` / `false` | `true` / `false` |
| `int` | `number` (dvojitá přesnost IEEE 754; celá čísla nad 2^53 ztrácejí přesnost) |
| `float` | `number` (NaN, ±Infinity, ±0 se zachovají) |
| `string` | `string` (bajty PHP UTF-8 se interpretují jako kódové jednotky UTF-16), i když název označuje funkci PHP |
| seznam `array` (klíče 0, 1, 2, … v pořadí) | nové `Array`, prvky se převádějí rekurzivně |
| jakékoli jiné `array` | nový prostý `Object`, hodnoty se převádějí rekurzivně |
| `Closure`, vyvolatelný objekt, volatelné pole jako `[$object, 'method']` | `function`, která volá volatelný objekt PHP |
| jakýkoli jiný objekt, `Generator`, zdroj | `undefined` |

Pole se smíšenými klíči (PHP, typ `[0 => 'a', 'name' => 'b']`) se mění na prosté objekty, protože pole v JS musí mít souvislé číselné indexy.

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

Protože se pole kopírují, změny provedené v JS zůstávají 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, které nelze volat, se nepřevádějí; dorazí jako `undefined` ([issue #18](https://github.com/inline0/phasis/issues/18)). Nejprve je převeďte na pole, například pomocí `get_object_vars()`, nebo zpřístupněte jejich metody jako uzávěry. Viz [Sdílené objekty](https://phasis.inline0.dev/cs/docs/interop/shared-objects).

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

| Hodnota JS | Výsledek PHP |
|---|---|
| `undefined`, `null` | `null` |
| `boolean` | `bool` |
| `number` (celé číslo v rozsahu int) | `int` (`-0` se změní na `0`) |
| `NaN`, `Infinity`, `-Infinity` | `NAN`, `INF`, `-INF` |
| `number` (ostatní) | `float` |
| `string` | `string` (UTF-8) |
| `bigint`, `symbol` | `null` |
| `function` | `null` |
| `Array` | seznam `array`, prvky se převádějí rekurzivně |
| `TypedArray` | seznam `array` čísel |
| jakýkoli jiný objekt (prosté objekty, `Error`, `Date`, `Map`, `Set`, `Promise`, `ArrayBuffer`, …) | asociativní `array` s vlastními datovými vlastnostmi, bez přístupových metod a vlastností obsahujících funkce |

Poslední řádek znamená, že objekty se stavem uloženým v interních slotech se převedou na prázdné pole: `new Date(0)`, `new Map([["a", 1]])` a `Promise.resolve(5)` všechny vracejí `[]`. Objekt `Error` vrací své vlastní `message` a `stack`. U objektu odkazujícího sám na sebe se vnitřní odkazy převedou 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)');              // []
```

Pokud některou z těchto hodnot potřebujete v PHP, proveďte převod uvnitř 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 hostitelských funkcí

Argumenty, které JS předává hostitelské funkci PHP, se u primitivních hodnot, polí a prostých objektů řídí stejnými pravidly, s těmito rozdíly:

| Hodnota JS | Argument PHP |
|---|---|
| `TypedArray`, `ArrayBuffer`, `DataView` | vlastní objekt enginu `JsValue`, bez převodu |
| objekt | asociativní `array` se všemi vlastními vlastnostmi, včetně přístupových metod (jejichž gettery se spustí) a vlastností obsahujících funkce |
| šipková funkce | `array` (obvykle prázdné) |
| `function` deklarované pomocí klíčového slova `function` nebo jakýkoli objekt odkazující sám na sebe | bez převodu: kopírování nikdy neskončí a PHP dojde paměť ([issue #21](https://github.com/inline0/phasis/issues/21)) |

Zatím nelze v hostitelské funkci přijmout zpětné volání JS. Přes tuto hranici předávejte data, nikoli funkce.

## Čísla a přesnost

Čísla JavaScript jsou čísla s dvojitou přesností IEEE 754. Celá čísla PHP používají nativní typ platformy (na všech moderních sestaveních 64bitový). Převod přesně zachovává celá čísla do ±2^53; větší celá čísla PHP se mění na `number` a ztrácejí přesnost po 16 desetinných číslicích. Potřebujete-li v JS přesné celočíselné výpočty s většími čísly, použijte v něm `BigInt` a výsledek předejte jako řetězec.

## Řetězce

Řetězce PHP jsou bajtová pole; řetězce JS tvoří sekvence 16bitových kódových jednotek. Engine je propojuje tak, že bajty PHP interpretuje jako UTF-8 a interně je znovu zakóduje do UTF-16. U platného vstupu UTF-8 se data neztratí. Neplatné bajty UTF-8 se předají beze změny, místo aby byly nahrazeny znakem U+FFFD.

`String.length`, indexovaný přístup (`s[0]`) i výběr podřetězce počítají podle specifikace ECMAScript 16bitové kódové jednotky — nikoli kódové body ani bajty. Řetězce obsahující znaky mimo základní rovinu, například `'🍕'`, proto obsahují `.length === 2` (dvě poloviny náhradního páru U+1F355).

## Stabilita při zpětném převodu

Prostá data přežijí zpětný převod: hodnota PHP tvořená skaláry, seznamy a asociativními poli, která vstoupí přes `setGlobal()` a vrátí se přes `eval()` nebo `call()`, vytvoří stejnou strukturu. Výjimkou jsou celočíselné hodnoty typu float, které se vrátí jako `int` (`1.0` vrací `1`).

Všechny ostatní hodnoty ve výše uvedených tabulkách, které se mapují na `null`, `undefined` nebo `[]`, zpětný převod nepřežijí.
