Liigu sisu juurde

Host-funktsioonid

Iga PHP sulund või väljakutsutav funktsioon, mis antakse mootorile setGlobal() kaudu, muutub väljakutsutavaks JS funktsiooniks. Käsitsi registreerimist, skeemi ega marshallimist pole vaja — andke funktsioon lihtsalt edasi.

Põhialused

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

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

Kui greet("world") käivitub JS-is:

  1. String "world" teisendatakse JS-st PHP-ks.
  2. PHP sulund käivitatakse teisendatud argumendiga.
  3. Tagastusväärtus teisendatakse PHP-st tagasi JS-ks.

Teisendamisel järgitakse väärtuse teisendamise reegleid. Argumendid on koopiad: JS massiiv või objekt saabub PHP massiivina ning selle muutmine PHP-s ei muuda väärtust JS-s.

Argumentide arv ja parameetrid

Mootor edastab väljakutsutavale funktsioonile JS argumendid järjekorras ja muutmata kujul; signatuuri see ei uuri. Tavalised sulundid eiravad lisaargumente, muutujapikkusega parameetrid koguvad need kokku. Puuduvaid argumente ei lisata: kui JS annab väljakutsutavale funktsioonile nõutust vähem argumente, viskab PHP vea ArgumentCountError. Määrake valikulistele parameetritele vaikeväärtused.

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

Tüübiteisendus

Argumendid teisendatakse PHP-ks reeglite JS→PHP järgi. Mootor kutsub teie funktsiooni koodist, mis deklareerib strict_types=1, seega kontrollib PHP skalaarsete parameetrite tüüpe rangelt ega teisenda väärtusi: JS tüüpi number string tüüpi parameetrile edastatuna põhjustab vea TypeError. Nagu iga host-funktsiooni PHP erand, jõuab see eval() kutsujani, mitte JS-ni (vt allpool).

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

Teisendage väärtus käsitsi sulundi sees ((string) $value) või kasutage ühendtüüpe (int|string).

Vea viskamine tagasi JS-i

Tavalisi PHP erandeid, mis visatakse host-funktsioonis, ei teisendata praegu JS vigadeks. Need läbivad kõik JS try-i ja catch-i ning .catch() käsitleja, jõudes PHP koodini, mis kutsus eval() või call(), ja säilitavad algse klassi (probleem #20). See kehtib ka vigaste argumentide korral tekkivate \Exception, \RuntimeException, \DivisionByZeroError ning \TypeError ja \ArgumentCountError PHP-i kohta.

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
}

Et tekitada viga, mida JS kood saab kinni püüda, visake üks mootori enda veaklassidest: Phasis\Exceptions\TypeError, RangeError või ReferenceError jõuavad JS-i vastava JS veatüübina; mis tahes muu Phasis\Exceptions\RuntimeError jõuab sinna lihtsa Error-na. Mõlemal juhul lisatakse teie veateade.

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

Rikkalike väärtuste tagastamine

Tagastusväärtus teisendatakse reeglite PHP→JS järgi. JS objekti literaali tagastamiseks tagastage PHP assotsiatiivne massiiv:

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

JS massiivi tagastamiseks tagastage järjestikune PHP loend:

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

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

PHP objekti, sealhulgas ReactPHP, Amp või Guzzle kaudu saadud PHP lubaduse tagastamine annab tulemuseks JS undefined (vt Väärtuse teisendamine).

Asünkroonsed host-funktsioonid

Host-funktsioonid on sünkroonsed. PHP lubadust ega muud jätkuva väärtuse objekti ei oodata ära: see teisendatakse väärtuseks undefined ja await hostFn() laheneb väärtuseks undefined. Tehke töö sünkroonselt ja tagastage tulemus; JS kood saab kutse siiski await-ga ootama panna, mis mähkib väärtuse lahendatud lubadusse:

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

Spetsiaalselt HTTP jaoks tagastab sisseehitatud fetch() juba päris JS lubaduse; selle saab suunata teie enda PHP kliendi kaudu, kasutades kohandatud päringutransporti.

Jõudluse märkused

Iga host-funktsiooni kutse ületab piiri PHP↔JS korra argumentide ja korra tagastusväärtuse jaoks. Mõlemad teisendused teevad koopiaid, seega on nende keerukus O(size of value) — primitiivväärtused on konstantse ajakuluga, massiivide ja objektide kulu sõltub nende suurusest.

Sagedasti käivitatavates sisemistes tsüklites eelistage üht rohkem tööd tegevat kutset paljudele väikestele ning vältige suurte massiivide edasi-tagasi saatmist, kui muutuvad vaid mõned väljad.