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(), argumentycall(), 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()icall()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.
$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:
$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). 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.
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.
$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:
$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) |
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.