Ugrás a tartalomhoz

Kezdő lépések

Telepítés

Shell
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 — a fetch() 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:

Dockerfile
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

Shell
./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
<?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

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

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

PHP
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

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