Začínáme
Instalace
composer require phasis/phasis
Phasis vyžaduje PHP 8.2 nebo novější a tři rozšíření, která všechna potřebuje composer.json:
mbstring— práce s řetězci.bcmath— aritmetika BigInt a práce s čísly s celočíselnou přesností.curl— výchozí přenosová vrstvafetch().
mbstring a curl jsou povolená ve většině sestavení. bcmath často chybí: oficiální obrazy php Docker (php:8.x-cli, php:8.x-fpm) ho neobsahují a některé distribuce ho balí samostatně (php-bcmath). Bez něj Composer odmítne nainstalovat Phasis. Do Dockerfile založeného na oficiálním obrazu přidejte:
RUN docker-php-ext-install bcmath
Spusťte php -m a zjistěte, která rozšíření vaše sestavení obsahuje.
ext-intl je volitelné. Phasis funguje i bez něj, ale celá rodina Intl.* (Collator, NumberFormat, DateTimeFormat, PluralRules, Locale, DisplayNames, ListFormat, RelativeTimeFormat, Segmenter, DurationFormat) a kalendáře Temporal jiné než ISO (hebrew, islamic, japanese, persian atd.) ho vyžadují. Nainstalujte ho, kdykoli hostitelská aplikace zpřístupňuje JS pro formátování závislé na národním prostředí. Volitelné je také ext-openssl: šifrování crypto.subtle AES-CBC, AES-CTR a AES-GCM ho vyžaduje, zatímco digesty a HMAC fungují bez něj.
Bez exec, FFI, Node.js i vlastního nativního rozšíření.
Spuštění skriptu z CLI
./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
CLI vypisuje to, co skript zapíše pomocí console.log; hodnotu posledního výrazu nevypisuje.
Úplný seznam příkazů najdete v referenční příručce CLI.
Vložení do 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
Instance Engine uchovává svůj globální stav mezi voláními, takže následující volání eval() uvidí vše, co jste definovali dříve.
console.log a související funkce přidávají výstup do vyrovnávací paměti enginu namísto zápisu na stdout. Přečtěte si ho pomocí getConsoleOutput() (jeden řetězec, řádky spojené pomocí \n) nebo getConsoleLines() (seznam řádků) a vyprázdněte ho pomocí clearConsole().
Studený start je rychlý: předem se instaluje jen jádro potřebné pro načtení specifikace (Object, Function, Array, Promise, Symbol, BigInt, RegExp a typy chyb). Map, TypedArray, Temporal, Intl, URL, fetch, celé balíčky Web Platform / Fetch a vše ostatní se transparentně načítá při prvním čtení. Předáním new Engine(eager: true) nainstalujete při vytváření všechny vestavěné prvky — hodí se pro nástroje ověřující shodu se specifikací, které kontrolují deskriptory ještě před jejich čtením; při běžném vkládání se používá zřídka.
Volání funkcí JS z PHP
$engine->eval('function add(a, b) { return a + b; }');
echo $engine->call('add', 2, 3);
// 5
call() vyhledá funkci podle názvu na globálním objektu a zavolá ji s dodanými argumenty PHP (automaticky převedenými na hodnoty JS).
Zpřístupnění volatelné funkce PHP jako funkce JS
$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")');
Uzávěry PHP se obalí jako funkce JS. Při volání se jejich argumenty převedou z JS na PHP a návratové hodnoty zpět z PHP na JS.
Objekty PHP
Objekty PHP zatím nelze sdílet s JavaScript. setGlobal() s běžným objektem PHP sváže undefined (sledováno v problému #18). Data předávejte jako pole, která se zkopírují do JS, a chování zpřístupněte jako uzávěry:
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
Podrobnosti a další možnosti náhradního řešení najdete v části Sdílené objekty.
Limity prostředků
$engine = new Engine();
$engine->setLimit('maxLoopIterations', 100_000);
maxLoopIterations (výchozí 100,000) je jediný limit, který lze měnit za běhu, a v současnosti se vztahuje pouze na kód spouštěný interpretem procházejícím strom. Smyčky spouštěné bajtkódovým VM (těla funkcí a většina kódu nejvyšší úrovně) se nepočítají, takže samotný limit nezastaví while (true) {} (problém #19). Po dosažení limitu lze chybu zachytit v JS; pokud ji nikdo nezachytí, dostane se do PHP jako Phasis\Exceptions\InternalError, podtřída Phasis\Exceptions\RuntimeError.
Engine má také pevný limit zásobníku volání 4,096 rámců (a kontrolní limit 8,192 rámců pro transpiled uzávěry); jeho překročení vyvolá chybu „Maximum call stack size exceeded“, opět jako InternalError. Časový limit není stanoven, proto nespouštějte nedůvěryhodný kód bez vlastního limitu max_execution_time PHP nebo v samostatném procesu. Viz API: setLimit.
Další kroky
- Referenční příručka API — úplné rozhraní
Phasis\Engine. - Propojení — pravidla převodu hodnot, hostitelské funkce, objekty PHP.
- Kompatibilita — implementované funkce a pokrytí test262.
- Parser a Formátovač — parsování a formátování JavaScript bez jeho spouštění.
- Pokročilé informace — architektura, bajtkódový VM, benchmarky a testování proti referenční implementaci.