Kezdő lépések
Telepítés
composer require phasis/phasis
A Phasis használatához PHP 8.2 vagy újabb verziója szükséges, három bővítménnyel, amelyeket mind a composer.json igényel:
mbstring— karakterláncok kezelése.bcmath— BigInt-aritmetika és egész számok pontos kezelése.curl— afetch()mögötti alapértelmezett transzport.
A mbstring és a curl a legtöbb buildben engedélyezve van. A bcmath viszont gyakran nincs: a hivatalos php Docker-képek (php:8.x-cli, php:8.x-fpm) kihagyják, egyes disztribúciók pedig külön csomagolják (php-bcmath). Nélküle a Composer nem hajlandó telepíteni a Phasis csomagot. A hivatalos képre épülő Dockerfile-ban adja hozzá ezt:
RUN docker-php-ext-install bcmath
Futtassa a php -m parancsot, hogy megtudja, mely bővítmények érhetők el a buildben.
A ext-intl opcionális. A Phasis nélküle is működik, de a teljes Intl.* családnak (Collator, NumberFormat, DateTimeFormat, PluralRules, Locale, DisplayNames, ListFormat, RelativeTimeFormat, Segmenter, DurationFormat) és a nem ISO típusú Temporal-naptáraknak (hebrew, islamic, japanese, persian stb.) szüksége van rá. Telepítse, ha a gazdaalkalmazás olyan JS lehetőséget biztosít, amely lokalizációt figyelembe vevő formázást végez. A ext-openssl szintén opcionális: a crypto.subtle AES-CBC, AES-CTR és AES-GCM titkosításhoz szükséges, míg a digest és a HMAC nélküle is működik.
Nincs exec, FFI, Node.js vagy egyéni natív bővítmény.
Szkript futtatása a CLI parancssorból
./vendor/bin/phasis -e 'console.log(1 + 2 * 3)'
# 7
./vendor/bin/phasis -e 'console.log([1, 2, 3].map(x => x * x).join(","))'
# 1,4,9
./vendor/bin/phasis path/to/script.js
A CLI kiírja, amit a szkript a console.log segítségével kiír; az utolsó kifejezés értékét nem jeleníti meg.
A teljes parancskészletet lásd a CLI-referenciában.
Beágyazás a PHP alkalmazásba
<?php
require __DIR__ . '/vendor/autoload.php';
use Phasis\Engine;
$engine = new Engine();
// Evaluate an expression
$result = $engine->eval('1 + 2 * 3');
echo $result;
// 7
// Execute a script file
$engine->execFile('path/to/script.js');
// Bridge a PHP value into the JS global scope
$engine->setGlobal('config', ['debug' => true, 'version' => '1.0']);
$engine->eval('console.log(config.version)');
// console output is buffered on the engine, not printed
echo $engine->getConsoleOutput();
// 1.0
A Engine példánya megőrzi globális állapotát a hívások között, így a későbbi eval() hívások látják mindazt, amit korábban definiált.
A console.log és társai a motor egyik pufferébe írnak a stdout helyett. Olvassa ki a getConsoleOutput() segítségével (egy karakterlánc, a sorokat \n kapcsolja össze) vagy a getConsoleLines() segítségével (sorok listája), a puffert pedig ürítse ki ezzel: clearConsole().
A hidegindítás gyors: kezdetben csak a specifikáció betöltéséhez szükséges mag (Object, Function, Array, Promise, Symbol, BigInt, RegExp és a hibatípusok) települ. A Map, TypedArray, Temporal, Intl, URL, fetch, a teljes Web Platform / Fetch Pack és minden más csak az első olvasáskor, átláthatóan töltődik be. Adja meg a new Engine(eager: true) beállítást, hogy konstrukciókor minden beépített elem települjön — ez olyan megfelelőségi eszközöknél hasznos, amelyek olvasás előtt vizsgálják meg a leírókat; normál beágyazáshoz ritkán szükséges.
PHP-ből JS-függvények hívása
$engine->eval('function add(a, b) { return a + b; }');
echo $engine->call('add', 2, 3);
// 5
A call() név alapján megkeresi a függvényt a globális objektumon, és a megadott PHP argumentumokkal hívja meg (amelyek automatikusan JS értékekké alakulnak).
PHP-beli hívható elem közzététele JS-függvényként
$engine->setGlobal('fetchData', function (string $url): array {
return json_decode(file_get_contents($url), true);
});
$engine->eval('const users = fetchData("https://api.example.com/users")');
A PHP-beli lezárások JS-függvényekként kapnak burkolót. A híváskor argumentumaik JS-ről PHP-ra, visszatérési értékeik pedig PHP-ről JS-ra alakulnak.
PHP-objektumok
A PHP objektumai egyelőre nem oszthatók meg a JavaScript használatával. A sima PHP objektummal végzett setGlobal()-kötés a undefined értéket rendeli hozzá (a #18. számú issue-ban követhető). Az adatokat tömbként adja át, amelyek bemásolódnak a JS-be, a viselkedést pedig tegye elérhetővé lezárásokként:
class Counter {
public int $value = 0;
public function increment(): void { $this->value++; }
}
$counter = new Counter();
$engine->setGlobal('increment', fn () => $counter->increment());
$engine->eval('increment(); increment();');
echo $counter->value;
// 2
A részletekért és további kerülőmegoldásokért lásd a Megosztott objektumok oldalt.
Erőforráskorlátok
$engine = new Engine();
$engine->setLimit('maxLoopIterations', 100_000);
A maxLoopIterations (alapértelmezett értéke 100,000) az egyetlen futás közben módosítható korlát, és jelenleg csak a faalapú értelmező által futtatott kódra vonatkozik. A bájtkód-VM által futtatott ciklusokat (függvénytörzsek és a legtöbb legfelső szintű kód) nem számolja, ezért önmagában a korlát nem állítja meg a while (true) {} működését (19. hibajegy). A korlát elérésekor a hiba elkapható a JS segítségével; ha nem kapják el, PHP formájában jut el a Phasis\Exceptions\InternalError-hoz, amely a Phasis\Exceptions\RuntimeError egyik alosztálya.
A motorban rögzített 4,096-keretes hívásveremkorlát van (transzpált lezárásoknál pedig 8,192-keretes védelemmel); túllépésekor „Maximum call stack size exceeded” hiba keletkezik, ismét InternalError formájában. Nincs valós időn alapuló korlát, ezért nem megbízható kódot a PHP saját max_execution_time korlátja alatt vagy külön folyamatban futtasson. Lásd: API: setLimit.
Következő lépések
- API-referencia — a teljes
Phasis\Engine-felület. - Együttműködés — értékkonverziós szabályok, gazdagépi függvények, PHP-objektumok.
- Kompatibilitás — mi van megvalósítva, test262-lefedettség.
- Parser és Formázó — JavaScript feldolgozása és formázása futtatás nélkül.
- Haladó témák — architektúra, bájtkód-VM, benchmarkok, orákulumtesztek.