Gå til indhold

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() og call() 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.

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

Da arrays kopieres, forbliver ændringer i JS i JS:

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

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

Konvertér dem inde i JS, når du har brug for en af disse værdier i 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: 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.