Vai al contenuto

Conversione dei valori

Ogni chiamata che attraversa il confine PHP↔JS — setGlobal, call o una funzione host invocata da JS — passa attraverso un convertitore:

  • PHP → JS quando i valori host entrano nel motore (setGlobal(), argomenti call(), valori restituiti dalle funzioni host).
  • JS → PHP quando i valori JS escono. Ci sono due percorsi con regole leggermente diverse: i risultati di eval() e call(), e gli argomenti che JS passa a una funzione host.

La conversione copia i valori. Le primitive restano primitive, array e oggetti vengono copiati in nuove strutture e le funzioni richiamabili PHP sono racchiuse in funzioni JS. Nulla viene condiviso per riferimento, fatta eccezione per le funzioni richiamabili stesse.

PHP → JS

Valore PHP Risultato JS
null null
true / false true / false
int number (double IEEE 754; gli interi oltre 2^53 perdono precisione)
float number (NaN, ±Infinity, ±0 preservati)
string string (byte UTF-8 PHP interpretati come unità di codice UTF-16), anche quando nomina una funzione PHP
array elenco (chiavi 0, 1, 2, … in ordine) nuovo Array, elementi convertiti ricorsivamente
qualsiasi altro array nuovo Object semplice, valori convertiti ricorsivamente
Closure, oggetto invocabile, array richiamabile come [$object, 'method'] function che invoca la funzione richiamabile PHP
qualsiasi altro oggetto, Generator, risorsa undefined

Gli array con chiavi miste (PHP [0 => 'a', 'name' => 'b']) diventano oggetti semplici perché gli array JS devono avere indici interi contigui.

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

Poiché gli array vengono copiati, le modifiche di JS rimangono in JS:

PHP
$list = [1, 2, 3];
$engine->setGlobal('list', $list);
$engine->eval('list.push(4)');
count($list);                 // 3
$engine->eval('list.length'); // 4

Gli oggetti PHP non richiamabili non vengono convertiti: arrivano come undefined (problema n. 18). Convertili prima in array, per esempio con get_object_vars(), oppure esponi i loro metodi tramite closure. Vedi Oggetti condivisi.

JS → PHP: risultati eval() e call()

Valore JS Risultato PHP
undefined, null null
boolean bool
number (intero, nell'intervallo int) int (-0 diventa 0)
NaN, Infinity, -Infinity NAN, INF, -INF
number (altro) float
string string (UTF-8)
bigint, symbol null
function null
Array elenco array, elementi convertiti ricorsivamente
TypedArray elenco array di numeri
qualsiasi altro oggetto (oggetti semplici, Error, Date, Map, Set, Promise, ArrayBuffer, …) array associativo delle sue proprietà dati, escluse quelle accessorie e quelle con valori funzione

L'ultima riga indica che gli oggetti il cui stato risiede in slot interni vengono convertiti in un array vuoto: new Date(0), new Map([["a", 1]]) e Promise.resolve(5) restituiscono tutti []. Un Error restituisce il proprio message e stack. Un oggetto che fa riferimento a sé stesso converte i propri riferimenti interni in 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)');              // []

Esegui la conversione all'interno di JS quando ti serve uno di questi valori in 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: argomenti delle funzioni host

Gli argomenti che JS passa a una funzione host PHP seguono le stesse regole per primitive, array e oggetti semplici, con queste differenze:

Valore JS Argomento PHP
TypedArray, ArrayBuffer, DataView oggetto JsValue del motore, non convertito
oggetto array associativo di tutte le sue proprietà, incluse quelle accessorie (i cui getter vengono eseguiti) e quelle con valori funzione
funzione freccia un array (di solito vuoto)
function dichiarata con la parola chiave function, o qualsiasi oggetto che fa riferimento a sé stesso nessuna conversione: la copia non termina mai e PHP esaurisce la memoria (problema n. 21)

Al momento non è possibile ricevere una callback JS in una funzione host. Passa dati, non funzioni, attraverso questo confine.

Numeri e precisione

I numeri JavaScript sono double IEEE 754. Gli interi PHP sono nativi della piattaforma (a 64 bit in ogni build moderna). La conversione conserva esattamente gli interi fino a ±2^53; gli interi PHP più grandi diventano number e perdono precisione oltre le 16 cifre decimali. Usa BigInt all'interno di JS se ti servono calcoli esatti con interi più grandi e passa il risultato come stringa.

Stringhe

Le stringhe PHP sono array di byte; le stringhe JS sono sequenze di unità di codice UTF-16. Il motore le converte interpretando i byte PHP come UTF-8 e ricodificandoli internamente in UTF-16. La conversione non comporta perdite per gli input UTF-8 validi. I byte che non sono UTF-8 valido vengono mantenuti invariati, anziché essere sostituiti con U+FFFD.

String.length, l'accesso tramite indice (s[0]) e lo slicing contano tutti le unità di codice UTF-16 secondo le specifiche ECMAScript, non i punti di codice né i byte. Le stringhe contenenti caratteri astrali come '🍕' hanno quindi .length === 2 (le due metà surrogate di U+1F355).

Stabilità del percorso di andata e ritorno

I dati semplici sopravvivono al percorso di andata e ritorno: un valore PHP composto da scalari, elenchi e array associativi, che entra tramite setGlobal() e torna tramite eval() o call(), produce la stessa struttura, salvo che i float interi tornano come int (1.0 restituisce 1).

Tutti gli altri valori nelle tabelle sopra che vengono convertiti in null, undefined o [] non sopravvivono al percorso di andata e ritorno.