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