Väärtuste teisendamine
Iga kõne, mis ületab piiri PHP↔JS — setGlobal, call, host-funktsiooni kutsumine JS-st — läbib teisendaja:
- PHP → JS, kui hosti väärtused liiguvad mootorisse (
setGlobal(),call()argumendid, host-funktsioonide tagastusväärtused). - JS → PHP, kui JS väärtused liiguvad tagasi välja. Kasutusel on kaks veidi erinevate reeglitega rada:
eval()jacall()tulemused ning argumendid, mille JS host-funktsioonile edastab.
Teisendamisel tehakse koopia. Primitiivid teisenduvad primitiivideks, massiivid ja objektid kopeeritakse uutesse struktuuridesse ning PHP-i kutsutavad objektid pakendatakse JS funktsioonideks. Viite kaudu jagatakse ainult kutsutavaid objekte.
PHP → JS
| PHP väärtus | JS tulemus |
|---|---|
null |
null |
true / false |
true / false |
int |
number (IEEE 754 double; täisarvud üle 2^53 kaotavad täpsuse) |
float |
number (NaN, ±Infinity, ±0 säilivad) |
string |
string (UTF-8 PHP baite tõlgendatakse UTF-16 koodiühikutena), ka siis, kui see nimetab PHP funktsiooni |
loendi array (võtmed 0, 1, 2, … järjekorras) |
uus Array, elemendid teisendatakse rekursiivselt |
mis tahes muu array |
uus lihtne Object, väärtused teisendatakse rekursiivselt |
Closure, väljakutsutav objekt, väljakutsutav massiiv, näiteks [$object, 'method'] |
function, mis kutsub PHP-i väljakutsutavat objekti |
mis tahes muu objekt, Generator, ressurss |
undefined |
Segavõtmetega massiivid (PHP-i [0 => 'a', 'name' => 'b']) muutuvad lihtobjektideks, sest JS massiividel peab olema katkematu täisarvuline indeksiruum.
$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
Kuna massiivid kopeeritakse, jäävad JS muudatused JS-i:
$list = [1, 2, 3];
$engine->setGlobal('list', $list);
$engine->eval('list.push(4)');
count($list); // 3
$engine->eval('list.length'); // 4
PHP objektid, mida ei saa välja kutsuda, ei teisendu ja saabuvad kujul undefined (probleem #18). Teisenda need esmalt massiivideks, näiteks käsuga get_object_vars(), või avalikusta nende meetodid sulunditena. Vt Shared objects.
JS → PHP: tulemused eval() ja call() korral
| JS väärtus | PHP tulemus |
|---|---|
undefined, null |
null |
boolean |
bool |
number (täisarv, int-vahemikus) |
int (-0 muutub väärtuseks 0) |
NaN, Infinity, -Infinity |
NAN, INF, -INF |
number (muu) |
float |
string |
string (UTF-8) |
bigint, symbol |
null |
function |
null |
Array |
loend array, elemendid teisendatakse rekursiivselt |
TypedArray |
arvude loend array |
mis tahes muu objekt (lihtobjektid, Error, Date, Map, Set, Promise, ArrayBuffer, …) |
assotsiatiivne array, mis sisaldab objekti enda andmeomadusi; gettereid ja funktsiooniväärtusega omadusi ei arvestata |
Viimane rida tähendab, et objektid, mille olek asub sisemistes pesades, teisenduvad tühjaks massiiviks: new Date(0), new Map([["a", 1]]) ja Promise.resolve(5) tagastavad kõik []. Error tagastab enda message ja stack. Iseendale viitava objekti sisemised viited teisenduvad väärtuseks 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)'); // []
Kui vajad mõnda neist väärtustest PHP-s, teisenda see JS sees:
$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: host-funktsiooni argumendid
Argumendid, mille JS edastab host-funktsioonile PHP, järgivad primitiivide, massiivide ja lihtobjektide puhul samu reegleid, kuid erinevad järgmiselt:
| JS väärtus | PHP argument |
|---|---|
TypedArray, ArrayBuffer, DataView |
mootori enda JsValue objekt, teisendamata |
| objekt | assotsiatiivne array kõigi objekti enda omadustega, sealhulgas getteritega (mis käivitatakse) ja funktsiooniväärtusega omadustega |
| noolefunktsioon | array (tavaliselt tühi) |
function, mis on deklareeritud märksõnaga function, või mis tahes iseendale viitav objekt |
ei teisendata: kopeerimine ei lõpe ja PHP-l saab mälu otsa (probleem #21) |
Host-funktsioonis ei saa praegu vastu võtta JS tagasihelistusfunktsiooni. Edasta üle selle piiri andmeid, mitte funktsioone.
Arvud ja täpsus
JavaScript arvud on IEEE 754 topelttäpsusega arvud. PHP täisarvud kasutavad platvormi algset vormingut (kõigis tänapäevastes järkudes 64-bitine). Teisendamisel säilivad täisarvud täpselt kuni ±2^53-ni; sellest suuremad PHP täisarvud muutuvad väärtuseks number ja kaotavad täpsuse üle 16 kümnendkoha. Kui vajad suuremate täisarvudega täpset arvutamist, kasuta selleks JS-s BigInt ja edasta tulemus stringina.
Stringid
PHP stringid on baidimassiivid; JS stringid on UTF-16 koodiühikute jadad. Mootor teisendab need, tõlgendades PHP baite UTF-8-na ja kodeerides need sisemiselt uuesti UTF-16-na. Kehtiv UTF-8-sisend teisendub kadudeta. Kehtetu UTF-8-ga baidid edastatakse muutmata, selle asemel et asendada need märgiga U+FFFD.
String.length, indekseerimine (s[0]) ja lõikamine loendavad ECMAScripti spetsifikatsiooni järgi UTF-16 koodiühikuid, mitte koodipunkte ega baite. Seetõttu on astralsümboleid, näiteks '🍕', sisaldavatel stringidel .length === 2 (U+1F355 kaks asenduspaari poolt).
Edasi-tagasi teisendamise stabiilsus
Lihtandmed säilivad edasi-tagasi teisendamisel: skalaare, loendeid ja assotsiatiivseid massiive sisaldav väärtus PHP, mis siseneb setGlobal() kaudu ja tuleb tagasi eval() või call() kaudu, annab sama struktuuri, välja arvatud see, et täisarvulised ujukomaarvud tulevad tagasi kujul int (1.0 tagastab 1).
Kõik muu ülaltoodud tabelites, mis teisendub kujule null, undefined või [], ei säili edasi-tagasi teisendamisel.