Funkcie hostiteľa
Každá uzávierka alebo volateľný objekt PHP odovzdaný enginu cez setGlobal() sa stane volateľnou funkciou JS. Netreba ju ručne registrovať, definovať schému ani maršalovať — stačí odovzdať volateľný objekt.
Základy
$engine->setGlobal('greet', function (string $name): string {
return "Hello, $name!";
});
echo $engine->eval('greet("world")');
// Hello, world!
Keď sa greet("world") spustí v JS:
- Reťazec
"world"sa prevedie z JS na PHP. - Uzávierka PHP sa zavolá s prevedeným argumentom.
- Návratová hodnota sa prevedie z PHP späť na JS.
Prevod sa riadi pravidlami prevodu hodnôt. Argumenty sú kópie: pole alebo objekt JS sa odovzdá ako pole PHP a zmeny v PHP nezmenia hodnotu JS.
Počet argumentov a parametre
Engine odovzdáva volateľnému objektu argumenty JS pozične a bez zmien; signatúru nekontroluje. Bežné uzávierky ignorujú nadbytočné argumenty a variadické parametre ich zhromažďujú. Chýbajúce argumenty sa nedopĺňajú: ak JS odovzdá menej argumentov, než volateľný objekt vyžaduje, PHP vyvolá ArgumentCountError. Voliteľným parametrom nastavte predvolené hodnoty.
$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
Konverzia typov
Argumenty sa prevádzajú na PHP podľa pravidiel JS→PHP. Engine volá váš volateľný objekt z kódu deklarujúceho strict_types=1, preto PHP striktne kontroluje skalárne typy parametrov a nevykonáva konverziu: číslo JS odovzdané parametru string spôsobí TypeError. Rovnako ako každá výnimka PHP z funkcie hostiteľa sa dostane k volajúcemu eval(), nie do JS (pozrite nižšie).
$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
Typ môžete previesť ručne vnútri uzávierky ((string) $value) alebo použiť zjednotené typy (int|string).
Vyvolanie výnimky späť do JS
Bežné výnimky PHP vyvolané vo funkcii hostiteľa sa dnes neprevádzajú na chyby JS. Prechádzajú cez každý JS try / catch a obslužnú rutinu .catch() až ku kódu PHP, ktorý zavolal eval() alebo call(), pričom si zachovávajú pôvodnú triedu (chyba č. 20). Patria sem \Exception, \RuntimeException, \DivisionByZeroError a chyby PHP \TypeError a \ArgumentCountError vyvolané pri nesprávnych argumentoch.
$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
}
Ak chcete vyvolať chybu, ktorú dokáže zachytiť kód JS, vyvolajte jednu z vlastných tried chýb enginu: Phasis\Exceptions\TypeError, RangeError alebo ReferenceError sa v JS prejavia ako zodpovedajúci typ chyby JS; každá iná Phasis\Exceptions\RuntimeError sa prejaví ako obyčajná Error, v oboch prípadoch s vašou správou.
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
}
');
Vracanie komplexných hodnôt
Návratová hodnota sa prevedie podľa pravidiel PHP→JS. Objekt JS vytvorený priamo v kóde vrátite asociatívnym poľom 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);
');
Pole JS vrátite ako postupný zoznam PHP:
$engine->setGlobal('range', fn(int $a, int $b): array => range($a, $b));
$engine->eval('range(1, 5).reduce((a, b) => a + b)'); // 15
Vrátením objektu PHP vrátane prísľubu PHP z ReactPHP, Amp alebo Guzzle získate JS undefined (pozrite Prevod hodnôt).
Asynchrónne funkcie hostiteľa
Funkcie hostiteľa sú synchrónne. Na prísľub PHP ani iný objekt typu thenable sa nečaká: prevedie sa na undefined, takže await hostFn() sa vyrieši na undefined. Prácu vykonajte synchrónne a vráťte výsledok; kód JS môže volanie aj tak vykonať pomocou await, čím sa hodnota obalí do vyriešeného prísľubu:
$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();
');
Špecificky pre HTTP vstavaný fetch() už vracia skutočný prísľub JS a možno ho presmerovať cez vlastného klienta PHP pomocou vlastného transportu fetch.
Poznámky k výkonu
Každé volanie funkcie hostiteľa prekračuje hranicu PHP↔JS raz pri argumentoch a raz pri návratovej hodnote. Pri oboch prevodoch sa údaje kopírujú, takže ich náklady sú O(size of value) — primitívne hodnoty sa spracúvajú v konštantnom čase, pri poliach a objektoch náklady rastú úmerne ich veľkosti.
V často vykonávaných vnútorných slučkách uprednostnite jedno volanie, ktoré urobí viac práce, pred množstvom malých volaní. Vyhnite sa aj opakovanému odovzdávaniu veľkých polí, keď sa mení iba niekoľko položiek.