Konverzia hodnôt
Každé volanie cez hranicu PHP↔JS — setGlobal, call alebo hostiteľská funkcia zavolaná z JS — prechádza konvertorom:
- PHP → JS, keď hodnoty hostiteľa vstupujú do enginu (
setGlobal(), argumentycall(), návratové hodnoty hostiteľských funkcií). - JS → PHP, keď hodnoty JS smerujú von. Existujú dve cesty s mierne odlišnými pravidlami: výsledky z
eval()acall()a argumenty, ktoré JS odovzdáva hostiteľskej funkcii.
Konverzia vytvára kópie. Primitívne hodnoty sa mapujú na primitívne hodnoty, polia a objekty sa kopírujú do nových štruktúr a volateľné hodnoty PHP sa obaľujú ako funkcie JS. Nič sa nezdieľa prostredníctvom referencie, okrem samotných volateľných hodnôt.
PHP → JS
| Hodnota PHP | Výsledok JS |
|---|---|
null |
null |
true / false |
true / false |
int |
number (dvojitá presnosť IEEE 754; celé čísla nad 2^53 strácajú presnosť) |
float |
number (NaN, ±Infinity, ±0 sa zachovávajú) |
string |
string (bajty UTF-8 PHP sa interpretujú ako 16-bitové jednotky kódu UTF-16), aj keď označuje funkciu PHP |
zoznam array (kľúče 0, 1, 2, … v poradí) |
nové Array, prvky sa konvertujú rekurzívne |
ľubovoľné iné array |
nový obyčajný Object, hodnoty sa konvertujú rekurzívne |
Closure, volateľný objekt, volateľné pole, napríklad [$object, 'method'] |
function, ktorá volá volateľnú hodnotu PHP |
ľubovoľný iný objekt, Generator, zdroj |
undefined |
Polia so zmiešanými kľúčmi (PHP, [0 => 'a', 'name' => 'b']) sa menia na obyčajné objekty, pretože polia v JS musia mať súvislý rozsah celočíselných indexov.
$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
Keďže sa polia kopírujú, zmeny v JS zostanú 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, ktoré nemožno volať, sa nekonvertujú; dorazia ako undefined (problém č. 18). Najskôr ich preveďte na polia, napríklad pomocou get_object_vars(), alebo ich metódy sprístupnite ako uzávery. Pozrite si Zdieľané objekty.
JS → PHP: výsledky eval() a call()
| Hodnota JS | Výsledok PHP |
|---|---|
undefined, null |
null |
boolean |
bool |
number (celé číslo v rozsahu int) |
int (-0 sa zmení na 0) |
NaN, Infinity, -Infinity |
NAN, INF, -INF |
number (iné) |
float |
string |
string (UTF-8) |
bigint, symbol |
null |
function |
null |
Array |
zoznam array, prvky sa konvertujú rekurzívne |
TypedArray |
zoznam array čísel |
ľubovoľný iný objekt (obyčajné objekty, Error, Date, Map, Set, Promise, ArrayBuffer, …) |
asociatívne array jeho vlastných dátových vlastností; prístupové vlastnosti a vlastnosti s funkčnými hodnotami sa vynechajú |
Posledný riadok znamená, že objekty so stavom uloženým v interných slotoch sa konvertujú na prázdne pole: new Date(0), new Map([["a", 1]]) a Promise.resolve(5) vracajú []. Objekt Error vracia vlastné message a stack. Objekt, ktorý odkazuje sám na seba, konvertuje svoje vnútorné odkazy 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)'); // []
Keď potrebujete niektorú z týchto hodnôt v PHP, konvertujte ju priamo v 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 hostiteľských funkcií
Argumenty, ktoré JS odovzdáva hostiteľskej funkcii PHP, sa pri primitívnych hodnotách, poliach a obyčajných objektoch riadia rovnakými pravidlami, s týmito rozdielmi:
| Hodnota JS | Argument PHP |
|---|---|
TypedArray, ArrayBuffer, DataView |
vlastný objekt enginu JsValue, bez konverzie |
| objekt | asociatívne array všetkých jeho vlastných vlastností vrátane prístupových vlastností (ktorých gettery sa spustia) a vlastností s funkčnými hodnotami |
| funkcia so šípkou | array (zvyčajne prázdny) |
function deklarovaná pomocou kľúčového slova function alebo ľubovoľný objekt odkazujúci sám na seba |
bez konverzie: kopírovanie sa nikdy neskončí a PHP minie pamäť (problém č. 21) |
Hostiteľská funkcia zatiaľ nedokáže prijať spätné volanie JS. Cez túto hranicu odovzdávajte dáta, nie funkcie.
Čísla a presnosť
Čísla JavaScript sú dvojité čísla IEEE 754. Celočíselné hodnoty PHP zodpovedajú platforme (64-bitové v každom modernom zostavení). Konverzia zachová celé čísla až do ±2^53 presne; väčšie celé čísla PHP sa zmenia na number a pri viac ako 16 desatinných číslicach strácajú presnosť. Ak potrebujete presne počítať s väčšími celými číslami, použite v JS BigInt a výsledok odovzdajte ako reťazec.
Reťazce
Reťazce PHP sú polia bajtov; reťazce JS sú postupnosti 16-bitových jednotiek kódu UTF-16. Engine ich prepája tak, že bajty PHP interpretuje ako UTF-8 a následne ich interne prekóduje do UTF-16. Pri platnom vstupe UTF-8 sa nestratia žiadne údaje. Bajty, ktoré netvoria platné UTF-8, sa prenášajú bez zmeny namiesto toho, aby sa nahradili znakom U+FFFD.
String.length, prístup podľa indexu (s[0]) aj výrezy počítajú jednotky kódu UTF-16 podľa špecifikácie ECMAScriptu — nie kódové body ani bajty. Reťazce obsahujúce znaky mimo základnej viacjazyčnej roviny, ako napríklad '🍕', preto majú .length === 2 (dve náhradné polovice znaku U+1F355).
Stabilita pri spätnom prevode
Jednoduché dáta sa pri spätnom prevode zachovajú: hodnota PHP pozostávajúca zo skalárov, zoznamov a asociatívnych polí, ktorá vstúpi cez setGlobal() a vráti sa cez eval() alebo call(), vytvorí rovnakú štruktúru, s výnimkou celočíselných čísel s desatinnou časťou, ktoré sa vrátia ako int (1.0 vracia 1).
Všetko ostatné z vyššie uvedených tabuliek, čo sa mapuje na null, undefined alebo [], sa pri spätnom prevode nezachová.