Pagrindinės programos funkcijos
Bet kuri PHP uždaroji funkcija ar kitas iškviečiamasis objektas, perduotas varikliui per setGlobal(), tampa iškviečiamąja JS funkcija. Nereikia rankiniu būdu registruoti ar aprašyti schemos, taip pat nėra maršalizavimo — tiesiog perduokite iškviečiamąjį objektą.
Pagrindai
$engine->setGlobal('greet', function (string $name): string {
return "Hello, $name!";
});
echo $engine->eval('greet("world")');
// Hello, world!
Kai greet("world") vykdomas JS:
- Eilutė
"world"konvertuojama iš JS į PHP. - Iškviečiama PHP uždaroji funkcija, jai perduodant konvertuotą argumentą.
- Grąžinama reikšmė konvertuojama iš PHP atgal į JS.
Konvertuojama pagal reikšmių konvertavimo taisykles. Argumentai yra kopijos: JS masyvas arba objektas gaunamas kaip PHP masyvas, o pakeitimai PHP nekeičia JS reikšmės.
Argumentų skaičius ir parametrai
Variklis perduoda JS argumentus iškviečiamajam objektui poziciniu būdu, tokius, kokie jie yra; signatūros netikrina. Įprastos uždarosios funkcijos ignoruoja papildomus argumentus, o kintamojo skaičiaus parametrams jie surenkami. Trūkstami argumentai nepateikiami: jei JS perduoda mažiau argumentų, nei reikia iškviečiamajam objektui, PHP išmeta ArgumentCountError. Pasirinktiniams parametrams nustatykite numatytąsias reikšmes.
$engine->setGlobal('format', function (string $template, ...$args): string {
return vsprintf($template, $args);
});
echo $engine->eval('format("%s is %d years old", "Ada", 36)');
// Ada is 36 years old
Tipų konvertavimas
Argumentai konvertuojami į PHP pagal JS→PHP taisykles. Variklis iškviečia jūsų iškviečiamąjį objektą iš kodo, kuriame deklaruota strict_types=1, todėl PHP griežtai tikrina skaliarinių parametrų tipus ir jų automatiškai nekonvertuoja: JS skaičius, perduotas parametrui string, yra TypeError. Kaip ir bet kuri PHP išimtis iš pagrindinės programos funkcijos, ji pasiekia eval() iškvietėją, o ne JS (žr. toliau).
$engine->setGlobal('len', fn(string $s): int => strlen($s));
$engine->eval('len(42)'); // PHP TypeError: Argument #1 ($s) must be of type string, int given
Viduje uždarojoje funkcijoje konvertuokite reikšmę rankiniu būdu ((string) $value) arba naudokite jungtinius tipus (int|string).
Klaidų išmetimas į JS
Įprastos PHP išimtys, išmestos pagrindinės programos funkcijoje, šiuo metu nekonvertuojamos į JS klaidas. Jos pereina per visus JS try / catch ir .catch() apdorojimo blokus, pasiekia PHP kodą, iškvietusį eval() arba call(), ir išlaiko pradinę klasę (20 klaida). Tai apima \Exception, \RuntimeException, \DivisionByZeroError ir \TypeError bei \ArgumentCountError PHP išmetamas klaidas dėl netinkamų argumentų.
$engine->setGlobal('divide', function (int $a, int $b): int {
if ($b === 0) {
throw new \DivisionByZeroError('cannot divide by zero');
}
return intdiv($a, $b);
});
try {
$engine->eval('
try { divide(10, 0); } catch (e) { /* never runs */ }
');
} catch (\DivisionByZeroError $e) {
echo $e->getMessage(); // cannot divide by zero
}
Norėdami išmesti klaidą, kurią JS kodas gali pagauti, naudokite vieną iš paties variklio klaidų klasių: Phasis\Exceptions\TypeError, RangeError arba ReferenceError pasiekia JS kaip atitinkamo tipo JS klaidą, o bet kuri kita Phasis\Exceptions\RuntimeError pasiekia jį kaip paprastas Error; visais atvejais išsaugomas jūsų pranešimas.
use Phasis\Exceptions\RangeError;
$engine->setGlobal('divide', function (int $a, int $b): int {
if ($b === 0) {
throw new RangeError('cannot divide by zero');
}
return intdiv($a, $b);
});
$engine->eval('
try {
divide(10, 0);
} catch (e) {
console.log(e.name, e.message); // RangeError cannot divide by zero
}
');
Sudėtinių reikšmių grąžinimas
Grąžinama reikšmė konvertuojama pagal PHP→JS taisykles. Norėdami grąžinti JS objekto literalo reikšmę, grąžinkite PHP asociatyvųjį masyvą:
$engine->setGlobal('lookup', function (string $key): array {
return ['key' => $key, 'value' => 42, 'meta' => ['ts' => time()]];
});
$engine->eval('
const r = lookup("answer");
console.log(r.value, r.meta.ts);
');
Norėdami grąžinti JS masyvą, grąžinkite nuoseklų PHP sąrašą:
$engine->setGlobal('range', fn(int $a, int $b): array => range($a, $b));
$engine->eval('range(1, 5).reduce((a, b) => a + b)'); // 15
Grąžinus PHP objektą, įskaitant ReactPHP, Amp arba Guzzle grąžinamą PHP pažadą, gaunama JS undefined (žr. Reikšmių konvertavimas).
Asinchroninės pagrindinės programos funkcijos
Pagrindinės programos funkcijos yra sinchroninės. PHP pažadas ar kitas thenable objektas nelaukiamas: jis konvertuojamas į undefined, todėl await hostFn() išsprendžiamas kaip undefined. Atlikite darbą sinchroniškai ir grąžinkite rezultatą; JS kode vis tiek galite iškviesti await, kuris apgaubia reikšmę išspręstu pažadu:
$engine->setGlobal('loadBody', function (string $url): string {
return file_get_contents($url);
});
$engine->eval('
async function main() {
const body = await loadBody("https://example.com");
console.log(body.length);
}
main();
');
Kalbant konkrečiai apie HTTP, integruotas fetch() jau grąžina tikrą JS pažadą; jį galima nukreipti per savo PHP klientą naudojant tinkintą fetch transportą.
Našumo pastabos
Kiekvienas pagrindinės programos funkcijos iškvietimas pereina PHP↔JS ribą vieną kartą argumentams ir vieną kartą grąžinamai reikšmei. Abiem atvejais reikšmės kopijuojamos, todėl sąnaudos yra O(size of value) — primityvių reikšmių atveju jos pastovios, o masyvų ir objektų sąnaudos priklauso nuo jų dydžio.
Dažnai vykdomuose vidiniuose cikluose geriau atlikti daugiau darbo vienu iškvietimu nei daug mažų iškvietimų. Taip pat venkite pirmyn ir atgal perduoti didelius masyvus, jei pasikeičia tik keli laukai.