Przejdź do treści

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). 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.

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)

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.