Værtsfunktioner
Enhver PHP-closure eller callable, der gives til motoren via setGlobal(), bliver til en callable JS-funktion. Der kræves ingen manuel registrering, intet skema og ingen marshalling — giv blot funktionen videre.
Grundlæggende
$engine->setGlobal('greet', function (string $name): string {
return "Hello, $name!";
});
echo $engine->eval('greet("world")');
// Hello, world!
Når greet("world") kører i JS:
- Strengen
"world"konverteres fra JS til PHP. - PHP-closen kaldes med det konverterede argument.
- Returværdien konverteres fra PHP tilbage til JS.
Konverteringen følger reglerne for værdikonvertering. Argumenterne er kopier: et JS-array eller -objekt modtages som et PHP-array, og ændringer i PHP ændrer ikke værdien i JS.
Argumentantal og parametre
Motoren sender JS-argumenterne til den kaldbare funktion positionsvis, som de er; den undersøger ikke signaturen. Almindelige closures ignorerer ekstra argumenter, mens variadiske parametre samler dem. Manglende argumenter udfyldes ikke: hvis JS sender færre argumenter, end funktionen kræver, kaster PHP ArgumentCountError. Giv valgfrie parametre standardværdier.
$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
Typekonvertering
Argumenter konverteres til PHP efter reglerne for JS→PHP. Motoren kalder din callable fra kode, der erklærer strict_types=1, så PHP kontrollerer skalære parametertyper strengt og foretager ingen tvangskonvertering: et JS tal, der gives til en string-parameter, er en TypeError. Som enhver PHP-undtagelse fra en værtsfunktion når den kaldende kode i eval() i stedet for JS (se nedenfor).
$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
Foretag selv tvangskonvertering i closure-funktionen ((string) $value), eller brug union-typer (int|string).
Kast tilbage til JS
Almindelige PHP-undtagelser, der kastes i en værtsfunktion, konverteres i øjeblikket ikke til JS-fejl. De går gennem alle JS try / catch- og .catch()-håndteringer og når den PHP kode, der kaldte eval() eller call(), med deres oprindelige klasse (issue #20). Det gælder \Exception, \RuntimeException, \DivisionByZeroError og de \TypeError og \ArgumentCountError PHP, der opstår ved ugyldige argumenter.
$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
}
Hvis du vil rejse en fejl, som JS-kode kan fange, skal du kaste en af motorens egne fejklasser: Phasis\Exceptions\TypeError, RangeError eller ReferenceError ankommer i JS som den tilsvarende JS-fejltype, og enhver anden Phasis\Exceptions\RuntimeError ankommer som en almindelig Error, begge med din meddelelse.
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
}
');
Returnering af komplekse værdier
Returværdien konverteres efter reglerne for PHP→JS. Hvis du vil returnere et JS-objektliteral, skal du returnere et PHP-associativt array:
$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);
');
Hvis du vil returnere et JS-array, skal du returnere en sekventiel PHP-liste:
$engine->setGlobal('range', fn(int $a, int $b): array => range($a, $b));
$engine->eval('range(1, 5).reduce((a, b) => a + b)'); // 15
Returnering af et PHP-objekt, herunder et PHP-promise fra ReactPHP, Amp eller Guzzle, giver JS undefined (se Værdikonvertering).
Asynkrone værtsfunktioner
Værtsfunktioner er synkrone. Et PHP-promise eller et andet thenable-objekt afventes ikke: det konverteres til undefined, så await hostFn() opløses til undefined. Udfør arbejdet synkront, og returner resultatet; JS-kode kan stadig await kaldet, som indpakker værdien i et allerede løst promise:
$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();
');
For specifikt HTTP returnerer den indbyggede fetch() allerede et ægte JS-promise, som kan sendes gennem din egen PHP-klient med en brugerdefineret fetch-transport.
Ydelsesnoter
Hvert kald af en værtsfunktion krydser grænsen mellem PHP og JS én gang for argumenterne og én gang for returværdien. Begge konverteringer kopierer, så de er O(size of value) — primitive værdier tager konstant tid, mens arrays og objekter koster proportionalt med deres størrelse.
I tætte indre løkker bør du foretrække ét kald, der udfører mere arbejde, frem for mange små kald, og undgå at sende store arrays frem og tilbage, når kun få felter ændres.