
# Konwersja wartości

Każde wywołanie przekraczające granicę PHP↔JS — `setGlobal`, `call`, funkcja hosta wywołana z JS — przechodzi przez konwerter:

- **PHP → JS** gdy wartości hosta trafiają do silnika (`setGlobal()`, argumenty `call()`, wartości zwracane przez funkcje hosta).
- **JS → PHP** gdy wartości JS wracają na zewnątrz. Istnieją dwie ścieżki z nieco odmiennymi regułami: wyniki `eval()` i `call()` oraz argumenty przekazywane przez JS do funkcji hosta.

Konwersja polega na kopiowaniu. Wartości proste są mapowane na wartości proste, tablice i obiekty są kopiowane do nowych struktur, a wywoływalne wartości PHP są opakowywane jako funkcje JS. Nic nie jest współdzielone przez referencję poza samymi funkcjami wywoływalnymi.

## PHP → JS

| Wartość PHP | Wynik JS |
|---|---|
| `null` | `null` |
| `true` / `false` | `true` / `false` |
| `int` | `number` (double IEEE 754; liczby całkowite powyżej 2^53 tracą precyzję) |
| `float` | `number` (NaN, ±Infinity, ±0 zachowane) |
| `string` | `string` (bajty UTF-8 PHP interpretowane jako jednostki kodowe UTF-16), nawet jeśli wskazuje funkcję PHP |
| lista `array` (klucze 0, 1, 2, … w kolejności) | nowa `Array`, elementy konwertowane rekurencyjnie |
| dowolne inne `array` | nowy zwykły `Object`, wartości konwertowane rekurencyjnie |
| `Closure`, obiekt wywoływalny, tablica wywoływalna, np. `[$object, 'method']` | `function` wywołujący funkcję wywoływalną PHP |
| każdy inny obiekt, `Generator`, zasób | `undefined` |

Tablice z mieszanymi kluczami (PHP — `[0 => 'a', 'name' => 'b']`) stają się zwykłymi obiektami, ponieważ tablice w JS muszą mieć ciągły zakres indeksów całkowitych.

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

Ponieważ tablice są kopiowane, zmiany w JS pozostają w JS:

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

Obiekty PHP, które nie są wywoływalne, nie podlegają konwersji; trafiają jako `undefined` ([issue #18](https://github.com/inline0/phasis/issues/18)). Najpierw zamień je na tablice, na przykład za pomocą `get_object_vars()`, albo udostępnij ich metody jako funkcje domknięcia. Zobacz [Obiekty współdzielone](https://phasis.inline0.dev/pl/docs/interop/shared-objects).

## JS → PHP: wyniki `eval()` i `call()`

| Wartość JS | Wynik PHP |
|---|---|
| `undefined`, `null` | `null` |
| `boolean` | `bool` |
| `number` (całkowita, w zakresie typu int) | `int` (`-0` staje się `0`) |
| `NaN`, `Infinity`, `-Infinity` | `NAN`, `INF`, `-INF` |
| `number` (inne) | `float` |
| `string` | `string` (UTF-8) |
| `bigint`, `symbol` | `null` |
| `function` | `null` |
| `Array` | lista `array`, elementy konwertowane rekurencyjnie |
| `TypedArray` | lista `array` liczb |
| każdy inny obiekt (zwykłe obiekty, `Error`, `Date`, `Map`, `Set`, `Promise`, `ArrayBuffer`, …) | asocjacyjna `array` własnych właściwości danych, z pominięciem akcesorów i właściwości przechowujących funkcje |

Ostatni wiersz oznacza, że obiekty przechowujące stan w wewnętrznych slotach są konwertowane na pustą tablicę: `new Date(0)`, `new Map([["a", 1]])` i `Promise.resolve(5)` wszystkie zwracają `[]`. `Error` zwraca własne `message` i `stack`. Obiekt odwołujący się do samego siebie konwertuje swoje wewnętrzne odwołania 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)');              // []
```

Konwertuj wewnątrz JS, jeśli potrzebujesz jednej z tych wartości w 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: argumenty funkcji hosta

Argumenty przekazywane przez JS funkcji hosta PHP podlegają tym samym regułom co wartości proste, tablice i zwykłe obiekty, z następującymi różnicami:

| Wartość JS | Argument PHP |
|---|---|
| `TypedArray`, `ArrayBuffer`, `DataView` | własny obiekt `JsValue` silnika, bez konwersji |
| obiekt | asocjacyjna `array` wszystkich własnych właściwości, w tym akcesorów (których gettery są wywoływane) i właściwości przechowujących funkcje |
| funkcja strzałkowa | `array` (zwykle puste) |
| `function` zadeklarowana słowem kluczowym `function` lub dowolny obiekt, który odwołuje się do samego siebie | brak konwersji: kopiowanie nigdy się nie kończy, a w PHP kończy się pamięć ([issue #21](https://github.com/inline0/phasis/issues/21)) |

Nie ma jeszcze sposobu na odebranie wywołania zwrotnego JS w funkcji hosta. Przekazuj przez tę granicę dane, a nie funkcje.

## Liczby i precyzja

Liczby JavaScript są wartościami double IEEE 754. Liczby całkowite PHP są natywne dla platformy (64-bitowe w każdej nowoczesnej kompilacji). Konwersja zachowuje dokładnie liczby całkowite do ±2^53; większe liczby całkowite PHP stają się `number` i tracą precyzję powyżej 16 cyfr dziesiętnych. Użyj `BigInt` wewnątrz JS, jeśli potrzebujesz dokładnych obliczeń na większych liczbach całkowitych, a wynik przekaż jako ciąg znaków.

## Ciągi znaków

Ciągi znaków PHP są tablicami bajtów; ciągi znaków JS to sekwencje jednostek kodowych UTF-16. Silnik łączy je, interpretując bajty PHP jako UTF-8 i ponownie kodując je wewnętrznie do UTF-16. Proces ten nie powoduje utraty danych dla prawidłowego wejścia UTF-8. Bajty, które nie są prawidłowym UTF-8, są przekazywane bez zmian zamiast zastępowania ich przez U+FFFD.

`String.length`, dostęp indeksowany (`s[0]`) i wycinanie podciągów liczą jednostki kodowe UTF-16 zgodnie ze specyfikacją ECMAScript — nie punkty kodowe ani bajty. Ciągi znaków zawierające znaki spoza płaszczyzny podstawowej, takie jak `'🍕'`, mają więc `.length === 2` (dwie połówki surogatowe U+1F355).

## Stabilność konwersji w obie strony

Zwykłe dane zachowują się przy konwersji w obie strony: wartość PHP złożona ze skalarów, list i tablic asocjacyjnych, która trafia do środka przez `setGlobal()` i wraca przez `eval()` lub `call()`, daje tę samą strukturę, z wyjątkiem liczb zmiennoprzecinkowych będących liczbami całkowitymi, które wracają jako `int` (`1.0` zwraca `1`).

Wszystkie pozostałe elementy z powyższych tabel, które są mapowane na `null`, `undefined` lub `[]`, nie zachowują się przy konwersji w obie strony.
