Přejít k obsahu

Začínáme

Instalace

Shell
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á vrstva fetch().

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:

Dockerfile
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

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

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
<?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

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

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

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:

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

Podrobnosti a další možnosti náhradního řešení najdete v části Sdílené objekty.

Limity prostředků

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