Treci la conținut

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 lui call(), 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() și call(), 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.

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

Deoarece tablourile sunt copiate, modificările aduse în JS rămân în JS:

PHP
$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.

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)');              // []

Convertiți în interiorul lui JS când aveți nevoie de una dintre aceste valori în 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: 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.