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 vancall(), retourwaarden van hostfuncties). - JS → PHP wanneer waarden van JS terug naar buiten stromen. Er zijn twee paden met enigszins afwijkende regels: resultaten van
eval()encall(), 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.
$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:
$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.
$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:
$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.