Przejdź do treści

Pierwsze kroki

Instalacja

Powłoka
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 przez fetch().

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:

Dockerfile
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

Powłoka
./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
<?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

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

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

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:

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

Szczegóły i inne obejścia znajdziesz w sekcji Współdzielone obiekty.

Limity zasobów

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