Værdikonvertering
Hvert kald, der krydser grænsen mellem PHP og JS — setGlobal, call, en værtsfunktion kaldt fra JS — går gennem en konverter:
- PHP → JS, når værtsværdier føres ind i motoren (
setGlobal(),call()-argumenter, returværdier fra værtsfunktioner). - JS → PHP, når JS-værdier føres ud igen. Der er to veje med lidt forskellige regler: resultater fra
eval()ogcall()samt de argumenter, JS sender til en værtsfunktion.
Konvertering kopierer. Primitive værdier mappes til primitive værdier, arrays og objekter kopieres til nye strukturer, og PHP-callables indpakkes som JS-funktioner. Intet deles som reference bortset fra selve de callable objekter.
PHP → JS
| PHP-værdi | JS-resultat |
|---|---|
null |
null |
true / false |
true / false |
int |
number (IEEE 754 double; heltal over 2^53 mister præcision) |
float |
number (NaN, ±Infinity, ±0 bevares) |
string |
string (UTF-8-PHP-bytes fortolket som UTF-16-kodeenheder), også når den navngiver en PHP-funktion |
liste array (nøglerne 0, 1, 2, … i rækkefølge) |
nyt Array, elementer konverteres rekursivt |
ethvert andet array |
nyt almindeligt Object, værdier konverteres rekursivt |
Closure, objekt, der kan kaldes, callable array som [$object, 'method'] |
function, der kalder den callable PHP |
ethvert andet objekt, Generator, ressource |
undefined |
Arrays med blandede nøgler (PHPs [0 => 'a', 'name' => 'b']) bliver til almindelige objekter, fordi arrays i JS skal have sammenhængende heltalsindekser.
$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
Da arrays kopieres, forbliver ændringer i JS i JS:
$list = [1, 2, 3];
$engine->setGlobal('list', $list);
$engine->eval('list.push(4)');
count($list); // 3
$engine->eval('list.length'); // 4
PHP-objekter, der ikke kan kaldes, konverteres ikke; de ankommer som undefined (issue #18). Konvertér dem først til arrays, f.eks. med get_object_vars(), eller eksponér deres metoder som closure-funktioner. Se Delte objekter.
JS → PHP: eval()- og call()-resultater
| JS-værdi | PHP-resultat |
|---|---|
undefined, null |
null |
boolean |
bool |
number (heltal inden for int-området) |
int (-0 bliver til 0) |
NaN, Infinity, -Infinity |
NAN, INF, -INF |
number (andet) |
float |
string |
string (UTF-8) |
bigint, symbol |
null |
function |
null |
Array |
liste array, elementer konverteres rekursivt |
TypedArray |
liste array med tal |
ethvert andet objekt (almindelige objekter, Error, Date, Map, Set, Promise, ArrayBuffer, …) |
associativt array med dets egne dataegenskaber; accessorer og egenskaber med funktionsværdier springes over |
Den sidste række betyder, at objekter, hvis tilstand ligger i interne slots, konverteres til et tomt array: new Date(0), new Map([["a", 1]]) og Promise.resolve(5) returnerer alle []. Et Error returnerer sit eget message og stack. Et objekt, der refererer til sig selv, konverterer sine indre referencer til 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)'); // []
Konvertér dem inde i JS, når du har brug for en af disse værdier i 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: argumenter til værtsfunktioner
Argumenter, som JS sender til en PHP-værtsfunktion, følger de samme regler for primitive værdier, arrays og almindelige objekter med disse forskelle:
| JS-værdi | PHP-argument |
|---|---|
TypedArray, ArrayBuffer, DataView |
motorens eget JsValue-objekt, ikke konverteret |
| objekt | associativt array med alle dets egne egenskaber, herunder accessorer (hvis getters køres) og egenskaber med funktionsværdier |
| arrow-funktion | et (normalt tomt) array |
function erklæret med nøgleordet function eller ethvert objekt, der refererer til sig selv |
ingen konvertering: kopien afsluttes aldrig, og PHP løber tør for hukommelse (issue #21) |
Det er endnu ikke muligt at modtage et JS-callback i en værtsfunktion. Send data, ikke funktioner, over denne grænse.
Tal og præcision
JavaScript-tal er IEEE 754-doubles. PHP-heltal er platformens native type (64-bit i alle moderne builds). Konverteringen bevarer heltal op til ±2^53 nøjagtigt; større PHP-heltal bliver til number og mister præcision efter 16 decimalcifre. Brug BigInt inde i JS, hvis du har brug for præcis aritmetik med større heltal, og send resultatet ud som en streng.
Strenge
PHP-strenge er byte-arrays; JS-strenge er sekvenser af UTF-16-kodeenheder. Motoren forbinder dem ved at fortolke PHP-bytes som UTF-8 og omkode dem til UTF-16 internt. Dette er tabsfrit for gyldigt UTF-8-input. Bytes, der ikke er gyldig UTF-8, sendes videre uændret i stedet for at blive erstattet med U+FFFD.
String.length, indekseret adgang (s[0]) og udsnit tæller alle UTF-16-kodeenheder i henhold til ECMAScript-specifikationen — ikke kodepunkter og ikke bytes. Strenge med astrale tegn som '🍕' har derfor .length === 2 (de to surrogatdele af U+1F355).
Stabilitet ved tur-retur-konvertering
Almindelige data overlever en tur-retur-konvertering: en PHP-værdi bestående af skalære værdier, lister og associative arrays, som sendes ind via setGlobal() og kommer tilbage via eval() eller call(), har samme struktur, bortset fra at heltalsformaterede floats kommer tilbage som int (1.0 returnerer 1).
Alt andet i tabellerne ovenfor, der mappes til null, undefined eller [], overlever ikke en tur-retur-konvertering.