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
$engine->setGlobal('greet', function (string $name): string {
return "Hello, $name!";
});
echo $engine->eval('greet("world")');
// Hello, world!
Kui greet("world") käivitub JS-is:
- String
"world"teisendatakse JS-st PHP-ks. - PHP sulund käivitatakse teisendatud argumendiga.
- 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.
$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).
$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.
$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.
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:
$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:
$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:
$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.