Pierwsze kroki
Instalacja
composer require phasis/phasis
Phasis wymaga PHP 8.2 lub nowszej oraz trzech rozszerzeń, których wszystkie wymaga composer.json:
mbstring— obsługa ciągów znaków.bcmath— arytmetyka BigInt i obsługa liczb z precyzją całkowitą.curl— domyślny transport używany przezfetch().
mbstring i curl są włączone w większości kompilacji. bcmath często nie jest: oficjalne obrazy php Docker (php:8.x-cli, php:8.x-fpm) go nie zawierają, a niektóre dystrybucje udostępniają je osobno (php-bcmath). Bez niego Composer odmawia instalacji Phasis. W pliku Dockerfile opartym na oficjalnym obrazie dodaj:
RUN docker-php-ext-install bcmath
Uruchom php -m, aby sprawdzić, które rozszerzenia zawiera Twoja kompilacja.
ext-intl jest opcjonalne. Phasis działa bez niego, ale potrzebuje go cała rodzina Intl.* (Collator, NumberFormat, DateTimeFormat, PluralRules, Locale, DisplayNames, ListFormat, RelativeTimeFormat, Segmenter, DurationFormat) oraz kalendarze Temporal inne niż ISO (hebrew, islamic, japanese, persian itd.). Zainstaluj je, jeśli aplikacja hosta udostępnia JS, które wykorzystuje formatowanie uwzględniające ustawienia regionalne. ext-openssl również jest opcjonalne: szyfrowanie AES-CBC, AES-CTR i AES-GCM w crypto.subtle wymaga go, natomiast skróty i HMAC działają bez niego.
Bez exec, bez FFI, bez Node.js, bez własnego rozszerzenia natywnego.
Uruchamianie skryptu z poziomu 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 wypisuje to, co skrypt zapisuje za pomocą console.log; nie wypisuje wartości ostatniego wyrażenia.
Zobacz CLI dokumentację, aby poznać pełny zestaw poleceń.
Osadzanie w 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
Instancja Engine zachowuje swój globalny stan między wywołaniami, więc kolejne wywołania eval() widzą wszystko, co zdefiniowano wcześniej.
console.log i jego odpowiedniki dopisują dane do bufora w silniku zamiast zapisywać je na stdout. Odczytaj go za pomocą getConsoleOutput() (jeden ciąg, wiersze połączone przez \n) lub getConsoleLines() (lista wierszy), a następnie wyczyść za pomocą clearConsole().
Szybki start: początkowo instalowany jest tylko rdzeń niezbędny do załadowania specyfikacji (Object, Function, Array, Promise, Symbol, BigInt, RegExp oraz typy błędów). Map, TypedArray, Temporal, Intl, URL, fetch, pełne pakiety Web Platform / Fetch i cała reszta są przejrzyście doładowywane przy pierwszym odczycie. Przekaż new Engine(eager: true), aby zainstalować wszystkie wbudowane elementy podczas tworzenia instancji — przydatne w narzędziach zgodności, które sprawdzają deskryptory przed ich odczytem; rzadko potrzebne przy zwykłym osadzaniu.
Wywoływanie funkcji JS z PHP
$engine->eval('function add(a, b) { return a + b; }');
echo $engine->call('add', 2, 3);
// 5
call() wyszukuje funkcję według nazwy w obiekcie globalnym i wywołuje ją z przekazanymi argumentami PHP (automatycznie konwertowanymi na wartości JS).
Udostępnianie funkcji wywoływalnej PHP jako funkcji 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")');
Domknięcia PHP są opakowywane jako funkcje JS. Ich argumenty podczas wywołania są konwertowane z JS na PHP, a wartości zwracane są konwertowane z powrotem z PHP na JS.
Obiekty PHP
Obiektów PHP nie można jeszcze współdzielić z JavaScript. setGlobal() z prostym obiektem PHP wiąże undefined (śledzone w zgłoszeniu #18). Przekazuj dane jako tablice, które są kopiowane do JS, a zachowanie udostępniaj jako domknięcia:
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
Szczegóły i inne obejścia znajdziesz w sekcji Współdzielone obiekty.
Limity zasobów
$engine = new Engine();
$engine->setLimit('maxLoopIterations', 100_000);
maxLoopIterations (domyślnie 100,000) to jedyny limit, który można dostosować w czasie działania; obecnie dotyczy wyłącznie kodu uruchamianego przez interpreter przechodzący po drzewie. Pętle uruchamiane przez maszynę wirtualną kodu bajtowego (ciała funkcji i większość kodu najwyższego poziomu) nie są zliczane, więc sam limit nie zatrzymuje while (true) {} (zgłoszeniu #19). Gdy limit zostanie osiągnięty, błąd można przechwycić w JS; jeśli nie zostanie przechwycony, trafia do PHP jako Phasis\Exceptions\InternalError, podklasa Phasis\Exceptions\RuntimeError.
Silnik ma również stały limit stosu wywołań wynoszący 4,096 ramek (oraz zabezpieczenie dla transpilorowanych domknięć ograniczone do 8,192 ramek); jego przekroczenie powoduje błąd „Maximum call stack size exceeded”, ponownie jako InternalError. Nie ma limitu czasu rzeczywistego, dlatego nieufny kod uruchamiaj pod kontrolą własnego max_execution_time PHP lub w osobnym procesie. Zobacz API: setLimit.
Następne kroki
- API dokumentacja — pełny zakres
Phasis\Engine. - Współdziałanie — reguły konwersji wartości, funkcje hosta, obiekty PHP.
- Zgodność — zaimplementowane funkcje, pokrycie test262.
- Parser i Formater — parsowanie i formatowanie JavaScript bez jego wykonywania.
- Zaawansowane — architektura, maszyna wirtualna kodu bajtowego, testy wydajności i testowanie z użyciem wzorca.