Към съдържанието

Първи стъпки

Инсталиране

Обвивка
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, базиран на официалния образ, добавете:

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

PHP
$engine->eval('function add(a, b) { return a + b; }');
echo $engine->call('add', 2, 3);
// 5

call() търси функцията по име в глобалния обект и я извиква с подадените аргументи PHP (автоматично преобразувани в стойности JS).

Показване на извикваем обект PHP като функция 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")');

Затварянията PHP се обвиват като функции JS. При извикване аргументите им се преобразуват от JS в PHP, а върнатите стойности — обратно от PHP в JS.

Обекти PHP

Обектите PHP все още не могат да се споделят с JavaScript. setGlobal() с обикновен обект PHP свързва undefined (описано в issue #18). Подайте данните като масиви, които се копират в JS, а поведението изложете чрез затваряния:

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

Вижте Споделени обекти за подробности и други решения.

Ограничения на ресурсите

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

Следващи стъпки