Първи стъпки
Инсталиране
composer require phasis/phasis
Phasis изисква PHP 8.2 или по-нова версия с три разширения, всички нужни на composer.json:
mbstring— обработка на низове.bcmath— аритметика с BigInt и обработка на числа с целочислена точност.curl— транспортът по подразбиране задfetch().
mbstring и curl са включени в повечето компилации. bcmath често липсва: официалните образи php Docker (php:8.x-cli, php:8.x-fpm) не го включват, а някои дистрибуции го пакетират отделно (php-bcmath). Без него Composer отказва да инсталира Phasis. В Dockerfile, базиран на официалния образ, добавете:
RUN docker-php-ext-install bcmath
Изпълнете php -m, за да видите кои разширения са включени в компилацията ви.
ext-intl не е задължителен. Phasis работи и без него, но цялото семейство Intl.* (Collator, NumberFormat, DateTimeFormat, PluralRules, Locale, DisplayNames, ListFormat, RelativeTimeFormat, Segmenter, DurationFormat) и календарите Temporal извън ISO (hebrew, islamic, japanese, persian и др.) го изискват. Инсталирайте го, когато хост приложението предоставя JS за форматиране според локала. ext-openssl също не е задължителен: криптирането AES-CBC, AES-CTR и AES-GCM в crypto.subtle го изисква, но digest и HMAC работят и без него.
Без exec, FFI, Node.js или персонализирано нативно разширение.
Изпълнение на скрипт от 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 отпечатва написаното от скрипта чрез console.log; не отпечатва стойността на последния израз.
Вижте справочника за CLI с пълния набор от команди.
Вграждане в 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
Инстанцията на Engine запазва глобалното си състояние между извикванията, така че следващите извиквания на eval() виждат всичко, което сте дефинирали по-рано.
console.log и сродните му методи добавят към буфер в енджина, вместо да записват в stdout. Прочетете го с getConsoleOutput() (един низ с редове, съединени с \n) или getConsoleLines() (списък с редове), а буферът се изчиства с clearConsole().
Студеното стартиране е бързо: предварително се инсталира само ядрото, необходимо за зареждане на спецификацията (Object, Function, Array, Promise, Symbol, BigInt, RegExp и типовете грешки). Map, TypedArray, Temporal, Intl, URL, fetch, пълните пакети Web Platform / Fetch и всичко останало се създават прозрачно при първото прочитане. Подайте new Engine(eager: true), за да инсталирате всички вградени компоненти при създаването — полезно за инструменти за проверка на съответствието, които инспектират дескриптори преди прочитането им; рядко е нужно при обичайно вграждане.
Извикване на функции JS от PHP
$engine->eval('function add(a, b) { return a + b; }');
echo $engine->call('add', 2, 3);
// 5
call() търси функцията по име в глобалния обект и я извиква с подадените аргументи PHP (автоматично преобразувани в стойности JS).
Показване на извикваем обект PHP като функция 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")');
Затварянията PHP се обвиват като функции JS. При извикване аргументите им се преобразуват от JS в PHP, а върнатите стойности — обратно от PHP в JS.
Обекти PHP
Обектите PHP все още не могат да се споделят с JavaScript. setGlobal() с обикновен обект PHP свързва undefined (описано в issue #18). Подайте данните като масиви, които се копират в JS, а поведението изложете чрез затваряния:
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
Вижте Споделени обекти за подробности и други решения.
Ограничения на ресурсите
$engine = new Engine();
$engine->setLimit('maxLoopIterations', 100_000);
maxLoopIterations (по подразбиране 100,000) е единственото ограничение, което може да се настройва по време на изпълнение, и засега важи само за код, изпълняван от интерпретатора, който обхожда дървото. Циклите, изпълнявани от виртуалната машина за байткод (телата на функции и повечето код на най-горно ниво), не се броят, така че само това ограничение не спира while (true) {} (issue #19). При достигането му грешката може да бъде прихваната в JS; ако не бъде прихваната, достига PHP като Phasis\Exceptions\InternalError, подклас на Phasis\Exceptions\RuntimeError.
Енджинът има и фиксиран лимит от 4,096 кадъра за стека на извикванията (както и предпазен лимит от 8,192 кадъра за транспилирани затваряния); при надвишаването му се хвърля „Maximum call stack size exceeded“, отново като InternalError. Няма ограничение по реално време, затова изпълнявайте недоверен код под контрола на собственото max_execution_time на PHP или в отделен процес. Вижте API: setLimit.
Следващи стъпки
- Справочник API — пълният интерфейс на
Phasis\Engine. - Свързване — правила за преобразуване на стойности, функции на хоста, обекти PHP.
- Съвместимост — реализирани възможности и покритие на test262.
- Парсер и Форматиращ инструмент — анализирайте и форматирайте JavaScript, без да го изпълнявате.
- Разширени теми — архитектура, виртуална машина за байткод, бенчмаркове и тестване с оракул.