Ga naar de inhoud

Waardeconversie

Elke aanroep die de grens tussen PHP en JS passeert — setGlobal, call of een hostfunctie die wordt aangeroepen vanuit JS — gaat via een converter:

  • PHP → JS wanneer waarden van de host naar de engine stromen (setGlobal(), argumenten van call(), retourwaarden van hostfuncties).
  • JS → PHP wanneer waarden van JS terug naar buiten stromen. Er zijn twee paden met enigszins afwijkende regels: resultaten van eval() en call(), en de argumenten die JS aan een hostfunctie doorgeeft.

Conversie maakt kopieën. Primitieven worden omgezet naar primitieven, arrays en objecten worden gekopieerd naar nieuwe structuren, en callables van PHP worden verpakt als functies van JS. Behalve de callables zelf wordt er niets gedeeld via een referentie.

PHP → JS

Waarde van PHP Resultaat van JS
null null
true / false true / false
int number (IEEE 754 double; ints boven 2^53 verliezen precisie)
float number (NaN, ±Infinity, ±0 blijven behouden)
string string (UTF-8-bytes van PHP geïnterpreteerd als UTF-16-code-eenheden), ook wanneer dit de naam is van een functie van PHP
lijst array (sleutels 0, 1, 2, … in volgorde) nieuwe Array, elementen recursief geconverteerd
elke andere array nieuw gewoon Object, waarden recursief geconverteerd
Closure, aanroepbaar object, callable array zoals [$object, 'method'] function die de callable van PHP aanroept
elk ander object, Generator, resource undefined

Arrays met gemengde sleutels (PHPs [0 => 'a', 'name' => 'b']) worden gewone objecten, omdat arrays in JS een aaneengesloten ruimte voor gehele indexen moeten hebben.

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

Omdat arrays worden gekopieerd, blijven wijzigingen van JS binnen JS:

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

Objecten van PHP die niet callable zijn, kunnen niet worden geconverteerd; ze komen aan als undefined (issue #18). Zet ze eerst om naar arrays, bijvoorbeeld met get_object_vars(), of maak hun methoden beschikbaar als closures. Zie Gedeelde objecten.

JS → PHP: resultaten van eval() en call()

Waarde van JS Resultaat van PHP
undefined, null null
boolean bool
number (integraal, binnen int-bereik) int (-0 wordt 0)
NaN, Infinity, -Infinity NAN, INF, -INF
number (overig) float
string string (UTF-8)
bigint, symbol null
function null
Array lijst array, elementen recursief geconverteerd
TypedArray lijst array met getallen
elk ander object (gewone objecten, Error, Date, Map, Set, Promise, ArrayBuffer, …) associatieve array met de eigen data-eigenschappen, waarbij accessors en eigenschappen met functiewaarden worden overgeslagen

De laatste rij betekent dat objecten waarvan de status in interne slots is opgeslagen, worden omgezet naar een lege array: new Date(0), new Map([["a", 1]]) en Promise.resolve(5) retourneren allemaal []. Een Error retourneert zijn eigen message en stack. De interne verwijzingen van een object dat naar zichzelf verwijst, worden omgezet naar 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)');              // []

Converteer de waarde binnen JS wanneer je een van deze waarden nodig hebt 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: argumenten van hostfuncties

De argumenten die JS aan een hostfunctie van PHP doorgeeft, volgen dezelfde regels voor primitieven, arrays en gewone objecten, met deze verschillen:

Waarde van JS Argument van PHP
TypedArray, ArrayBuffer, DataView het eigen object van de engine van JsValue, niet geconverteerd
object associatieve array met alle eigen eigenschappen, waaronder accessors (waarvan getters worden uitgevoerd) en eigenschappen met functiewaarden
pijlfunctie een array (meestal lege)
function gedeclareerd met het sleutelwoord function, of elk object dat naar zichzelf verwijst geen conversie: de kopie wordt nooit voltooid en PHP raakt door zijn geheugen heen (issue #21)

Er is nog geen manier om een callback van JS te ontvangen in een hostfunctie. Geef gegevens door, geen functies.

Getallen en precisie

Getallen van JavaScript zijn doubles volgens IEEE 754. Gehele getallen van PHP zijn systeemeigen (64-bit in elke moderne build). De conversie behoudt gehele getallen tot ±2^53 exact; grotere gehele getallen van PHP worden omgezet naar number en verliezen precisie voorbij 16 decimale cijfers. Gebruik BigInt binnen JS als je exacte rekenkundige bewerkingen nodig hebt op grotere gehele getallen, en geef het resultaat door als tekenreeks.

Tekenreeksen

Tekenreeksen van PHP zijn bytearrays; tekenreeksen van JS zijn reeksen UTF-16-code-eenheden. De engine overbrugt dit door de bytes van PHP als UTF-8 te interpreteren en ze intern opnieuw naar UTF-16 te coderen. Dit verloopt zonder verlies voor geldige UTF-8-invoer. Bytes die geen geldige UTF-8 vormen, worden ongewijzigd doorgegeven in plaats van vervangen door U+FFFD.

String.length, geïndexeerde toegang (s[0]) en slicing tellen allemaal UTF-16-code-eenheden volgens de ECMAScript-specificatie — geen codepoints en geen bytes. Tekenreeksen met aanvullende tekens zoals '🍕' hebben daarom .length === 2 (de twee surrogaathelften van U+1F355).

Stabiliteit van de retourconversie

Gewone gegevens behouden hun structuur bij een retourconversie: een waarde van PHP met scalaren, lijsten en associatieve arrays die via setGlobal() wordt doorgegeven en via eval() of call() terugkomt, heeft dezelfde structuur. Een uitzondering zijn integrale floats, die terugkomen als int (1.0 retourneert 1).

Al het overige in de bovenstaande tabellen dat wordt omgezet naar null, undefined of [] behoudt de oorspronkelijke waarde niet bij een retourconversie.