Hoppa till innehåll

Värdfunktioner

Alla PHP-slutningar eller anropsbara funktioner som skickas till motorn via setGlobal() blir anropsbara funktioner i JS. Ingen manuell registrering, inget schema och ingen marshalling krävs — skicka bara funktionen.

Grunderna

PHP
$engine->setGlobal('greet', function (string $name): string {
    return "Hello, $name!";
});

echo $engine->eval('greet("world")');
// Hello, world!

När greet("world") körs i JS:

  1. Strängen "world" konverteras från JS till PHP.
  2. PHP-slutningen anropas med det konverterade argumentet.
  3. Returvärdet konverteras från PHP tillbaka till JS.

Konvertering följer reglerna för värdekonvertering. Argument kopieras: en JS-array eller ett objekt blir en PHP-array, och ändringar i PHP påverkar inte värdet i JS.

Aritet och parametrar

Motorn skickar de JS argumenten till funktionen i ordning, precis som de är; den granskar inte signaturen. Vanliga slutningar ignorerar extra argument och variadiska parametrar samlar in dem. Saknade argument fylls inte i: om JS skickar färre argument än funktionen kräver kastar PHP ArgumentCountError. Ange standardvärden för valfria parametrar.

PHP
$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

Typomvandling

Argument konverteras till PHP enligt reglerna för JS→PHP. Motorn anropar din funktion från kod som deklarerar strict_types=1, så PHP kontrollerar skalära parametertyper strikt och tvingar inte fram typomvandling: ett JS-tal som skickas till en parameter av typen string är ett TypeError. Precis som alla PHP-undantag från en värdfunktion når det anropet av eval() i stället för JS (se nedan).

PHP
$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

Tvinga antingen fram rätt typ manuellt i slutningen ((string) $value) eller använd unionstyper (int|string).

Kasta undantag tillbaka till JS

Vanliga PHP-undantag som kastas i en värdfunktion konverteras inte till JS-fel i dag. De passerar genom alla JS try/catch- och .catch()-hanterare och når PHP-koden som anropade eval() eller call(), med sin ursprungliga klass (ärende nr 20). Detta gäller \Exception, \RuntimeException, \DivisionByZeroError och \TypeError- och \ArgumentCountError-felen PHP kastar vid felaktiga argument.

PHP
$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
}

Om du vill kasta ett fel som kod i JS kan fånga kastar du en av motorns egna felklasser: Phasis\Exceptions\TypeError, RangeError eller ReferenceError blir motsvarande JS-feltyp i JS, medan alla andra Phasis\Exceptions\RuntimeError blir ett vanligt Error, båda med ditt meddelande.

PHP
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
    }
');

Returnera värden med rik struktur

Returvärdet konverteras enligt reglerna för PHP→JS. Om du vill returnera ett objekt av typen JS returnerar du en associativ array av typen PHP:

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);
');

Om du vill returnera en JS-array returnerar du en sekventiell lista av typen PHP:

PHP
$engine->setGlobal('range', fn(int $a, int $b): array => range($a, $b));

$engine->eval('range(1, 5).reduce((a, b) => a + b)');  // 15

Om du returnerar ett objekt av typen PHP, inklusive ett löfte av typen PHP från ReactPHP, Amp eller Guzzle, får du JS undefined (se Värdekonvertering).

Asynkrona värdfunktioner

Värdfunktioner är synkrona. Ett löfte av typen PHP eller ett annat thenable-objekt väntas inte in: det konverteras till undefined, så await hostFn() löses till undefined. Utför arbetet synkront och returnera resultatet; kod i JS kan fortfarande await anropet, vilket omsluter värdet i ett löst löfte:

PHP
$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();
');

För just HTTP returnerar den inbyggda fetch() redan ett riktigt löfte av typen JS och kan dirigeras via din egen klient av typen PHP med en anpassad fetch-transport.

Prestandaanmärkningar

Varje anrop av en värdfunktion passerar gränsen mellan PHP och JS en gång för argumenten och en gång för returvärdet. Båda konverteringarna gör en kopia, så de är O(size of value) — grundtyper tar konstant tid, medan arrayer och objekt kostar proportionellt mot sin storlek.

I täta inre loopar bör du föredra ett anrop som gör mer arbete framför många små anrop och undvika att skicka stora arrayer fram och tillbaka när bara några få fält ändras.