Gostiteljske funkcije
Vsaka zaprta funkcija ali klicni objekt PHP, ki ga posredujete mehanizmu prek setGlobal(), postane klicna funkcija JS. Ročna registracija, shema ali posredovanje podatkov ni potrebno — preprosto posredujte klicno funkcijo.
Osnove
$engine->setGlobal('greet', function (string $name): string {
return "Hello, $name!";
});
echo $engine->eval('greet("world")');
// Hello, world!
Ko se greet("world") izvede v JS:
- Niz
"world"se pretvori iz JS v PHP. - Zaprta funkcija PHP se pokliče s pretvorjenim argumentom.
- Vrnjena vrednost se pretvori iz PHP nazaj v JS.
Pretvorba sledi pravilom pretvorbe vrednosti. Argumenti so kopije: tabela ali objekt JS prispe kot tabela PHP, zato spremembe v PHP ne spremenijo vrednosti JS.
Število argumentov in parametri
Mehanizem posreduje JS argumente klicni funkciji po vrsti, takšne, kot so; podpisa ne preverja. Običajne zaprte funkcije prezrejo odvečne argumente, variadični parametri pa jih zberejo. Manjkajoči argumenti niso dodani: če JS posreduje manj argumentov, kot jih klicna funkcija zahteva, PHP sproži ArgumentCountError. Izbirnim parametrom določite privzete vrednosti.
$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
Prilagajanje tipov
Argumenti se pretvorijo v PHP po pravilih JS→PHP. Mehanizem pokliče vašo funkcijo iz kode, ki deklarira strict_types=1, zato PHP strogo preverja vrste skalarnih parametrov in jih ne pretvarja: število JS, posredovano parametru string, povzroči TypeError. Tako kot vsaka izjema PHP iz gostiteljske funkcije tudi ta doseže klicatelja eval(), namesto da bi dosegla JS (glejte spodaj).
$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
V zaprti funkciji lahko tip pretvorite ročno ((string) $value) ali uporabite unijske tipe (int|string).
Sprožanje napak v JS
Običajne izjeme PHP, sprožene v gostiteljski funkciji, se trenutno ne pretvorijo v napake JS. Potujejo skozi vse upravljalnike JS try/catch in .catch() ter dosežejo kodo PHP, ki je poklicala eval() ali call(), z ohranjenim izvirnim razredom (težava št. 20). To vključuje \Exception, \RuntimeException, \DivisionByZeroError ter napaki \TypeError in \ArgumentCountError PHP, ki se sprožita ob neveljavnih argumentih.
$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
}
Če želite sprožiti napako, ki jo lahko ujame koda JS, sprožite enega od lastnih razredov napak mehanizma: Phasis\Exceptions\TypeError, RangeError ali ReferenceError se v JS pojavijo kot ustrezna vrsta napake JS, vse druge napake Phasis\Exceptions\RuntimeError pa kot navadne napake Error, vsaka z vašim sporočilom.
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
}
');
Vračanje kompleksnih vrednosti
Vrnjena vrednost se pretvori po pravilih PHP→JS. Če želite vrniti objektni literal JS, vrnite asociativno tabelo PHP:
$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);
');
Če želite vrniti tabelo JS, vrnite zaporedni seznam PHP:
$engine->setGlobal('range', fn(int $a, int $b): array => range($a, $b));
$engine->eval('range(1, 5).reduce((a, b) => a + b)'); // 15
Če vrnete objekt PHP, vključno z obljubo PHP iz ReactPHP, Amp ali Guzzle, dobite JS undefined (glejte Pretvorba vrednosti).
Asinhrone gostiteljske funkcije
Gostiteljske funkcije so sinhrone. Objava PHP ali drug objekt thenable se ne čaka; pretvori se v undefined, zato se await hostFn() razreši v undefined. Delo opravite sinhrono in vrnite rezultat; koda JS lahko še vedno uporabi await za klic, ki vrednost ovije v razrešeno obljubo:
$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();
');
Za HTTP vgrajeni fetch() že vrne pravo obljubo JS, ki jo lahko usmerite prek lastnega odjemalca PHP s prenosom fetch po meri.
Opombe o zmogljivosti
Vsak klic gostiteljske funkcije enkrat prečka mejo PHP↔JS za argumente in enkrat za vrnjeno vrednost. Obe pretvorbi ustvarita kopijo, zato je njun strošek O(size of value) — primitivi zahtevajo konstanten čas, tabele in objekti pa čas, sorazmeren njihovi velikosti.
V vročih notranjih zankah raje opravite več dela z enim klicem kot z mnogimi majhnimi in ne prenašajte velikih tabel sem ter tja, kadar se spremeni le nekaj polj.