Conversia valorilor
Fiecare apel care traversează limita PHP↔JS — setGlobal, call, o funcție gazdă apelată din JS — trece printr-un convertor:
- PHP → JS când valorile gazdei ajung în motor (
setGlobal(), argumentele luicall(), valorile returnate de funcțiile gazdă). - JS → PHP când valorile JS sunt trimise înapoi. Există două căi cu reguli ușor diferite: rezultatele lui
eval()șicall(), respectiv argumentele pe care JS le transmite unei funcții gazdă.
Conversia copiază valorile. Primitivele se mapează la primitive, tablourile și obiectele sunt copiate în structuri noi, iar funcțiile apelabile PHP sunt încapsulate ca funcții JS. Nimic nu este partajat prin referință, cu excepția funcțiilor apelabile în sine.
PHP → JS
| Valoare PHP | Rezultat JS |
|---|---|
null |
null |
true / false |
true / false |
int |
number (double IEEE 754; întregii mai mari de 2^53 își pierd precizia) |
float |
number (NaN, ±Infinity, ±0 păstrate) |
string |
string (octeții UTF-8 PHP interpretați ca unități de cod UTF-16), chiar și când denumește o funcție PHP |
listă array (cheile 0, 1, 2, … în ordine) |
Array nou, elementele convertite recursiv |
orice alt array |
Object simplu nou, valorile convertite recursiv |
Closure, obiect apelabil, tablou apelabil precum [$object, 'method'] |
function care apelează funcția apelabilă PHP |
orice alt obiect, Generator, resursă |
undefined |
Tablourile cu chei mixte (PHP, [0 => 'a', 'name' => 'b']) devin obiecte simple, deoarece tablourile din JS trebuie să aibă o secvență continuă de indici întregi.
$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
Deoarece tablourile sunt copiate, modificările aduse în JS rămân în JS:
$list = [1, 2, 3];
$engine->setGlobal('list', $list);
$engine->eval('list.push(4)');
count($list); // 3
$engine->eval('list.length'); // 4
Obiectele PHP care nu sunt apelabile nu se convertesc; ajung ca undefined (problema #18). Convertiți-le mai întâi în tablouri, de exemplu cu get_object_vars(), sau expuneți-le metodele ca funcții anonime. Consultați Obiecte partajate.
JS → PHP: rezultate eval() și call()
| Valoare JS | Rezultat PHP |
|---|---|
undefined, null |
null |
boolean |
bool |
number (integral, în intervalul int) |
int (-0 devine 0) |
NaN, Infinity, -Infinity |
NAN, INF, -INF |
number (altul) |
float |
string |
string (UTF-8) |
bigint, symbol |
null |
function |
null |
Array |
listă array, elementele convertite recursiv |
TypedArray |
listă array de numere |
orice alt obiect (obiecte simple, Error, Date, Map, Set, Promise, ArrayBuffer, …) |
array asociativ, cu propriile proprietăți de date, omițând accesorii și proprietățile cu valori funcție |
Ultimul rând înseamnă că obiectele a căror stare se află în sloturi interne se convertesc într-un tablou gol: new Date(0), new Map([["a", 1]]) și Promise.resolve(5) returnează toate []. Un Error returnează propriile message și stack. Un obiect care face referire la sine își convertește referințele interne la 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)'); // []
Convertiți în interiorul lui JS când aveți nevoie de una dintre aceste valori în 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: argumentele funcțiilor gazdă
Argumentele pe care JS le transmite unei funcții gazdă PHP urmează aceleași reguli pentru primitive, tablouri și obiecte simple, cu următoarele diferențe:
| Valoare JS | Argument PHP |
|---|---|
TypedArray, ArrayBuffer, DataView |
propriul obiect JsValue al motorului, neconvertit |
| obiect | array asociativ cu toate proprietățile proprii, inclusiv accesorii (ale căror funcții getter sunt executate) și proprietăți cu valori funcție |
| funcție săgeată | un array (de obicei gol) |
function declarat cu cuvântul-cheie function sau orice obiect care face referire la sine |
fără conversie: copia nu se termină niciodată, iar PHP rămâne fără memorie (problema #21) |
Momentan nu există nicio modalitate de a primi un apel invers JS într-o funcție gazdă. Treceți date, nu funcții, peste această limită.
Numere și precizie
Numerele JavaScript sunt valori double IEEE 754. Întregii PHP sunt nativi platformei (64 de biți în fiecare versiune modernă). Conversia păstrează exact numerele întregi până la ±2^53; întregii PHP mai mari devin number și își pierd precizia după 16 cifre zecimale. Folosiți BigInt în JS dacă aveți nevoie de calcule exacte cu întregi mai mari și transmiteți rezultatul ca șir de caractere.
Șiruri de caractere
Șirurile PHP sunt tablouri de octeți; șirurile JS sunt secvențe de unități de cod UTF-16. Motorul face conversia interpretând octeții PHP ca UTF-8 și recodificându-i intern în UTF-16. Conversia nu pierde date pentru intrări UTF-8 valide. Octeții care nu sunt UTF-8 valid sunt transmiși neschimbați, în loc să fie înlocuiți cu U+FFFD.
String.length, accesul după index (s[0]) și extragerea de subșiruri numără unitățile de cod UTF-16, conform specificației ECMAScript — nu punctele de cod și nici octeții. Prin urmare, șirurile care conțin caractere astrale precum '🍕' au .length === 2 (cele două jumătăți surrogate ale lui U+1F355).
Stabilitatea conversiei dus-întors
Datele simple supraviețuiesc unei conversii dus-întors: o valoare PHP formată din scalari, liste și tablouri asociative, transmisă prin setGlobal() și returnată prin eval() sau call(), produce aceeași structură, cu excepția valorilor float întregi, care revin ca int (1.0 returnează 1).
Nimic altceva din tabelele de mai sus care se mapează la null, undefined sau [] nu poate fi convertit dus-întors.