Primii pași
Instalare
composer require phasis/phasis
Phasis necesită PHP 8.2 sau o versiune ulterioară, cu trei extensii, toate necesare pentru composer.json:
mbstring— gestionarea șirurilor.bcmath— operații aritmetice BigInt și gestionarea numerelor cu precizie întreagă.curl— transportul implicit din spatelefetch().
mbstring și curl sunt activate în majoritatea build-urilor. Adesea, bcmath nu este: imaginile oficiale php Docker (php:8.x-cli, php:8.x-fpm) nu îl includ, iar unele distribuții îl împachetează separat (php-bcmath). Fără acesta, Composer refuză să instaleze Phasis. Într-un Dockerfile bazat pe imaginea oficială, adăugați:
RUN docker-php-ext-install bcmath
Rulați php -m pentru a vedea ce extensii are build-ul dvs.
ext-intl este opțional. Phasis funcționează fără el, dar întreaga familie Intl.* (Collator, NumberFormat, DateTimeFormat, PluralRules, Locale, DisplayNames, ListFormat, RelativeTimeFormat, Segmenter, DurationFormat) și calendarele Temporal non-ISO (hebrew, islamic, japanese, persian etc.) au nevoie de el. Instalați-l ori de câte ori aplicația gazdă expune JS care afectează formatarea în funcție de localizare. Și ext-openssl este opțional: criptarea AES-CBC, AES-CTR și AES-GCM din crypto.subtle necesită această extensie, în timp ce rezumatele criptografice și HMAC funcționează fără ea.
Fără exec, fără FFI, fără Node.js, fără extensii native personalizate.
Rulați un script din 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 afișează ce scrie scriptul cu console.log; nu afișează valoarea ultimei expresii.
Consultați referința CLI pentru cu setul complet de comenzi.
Încorporați în 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
Instanța Engine își păstrează starea globală între apeluri, astfel că invocările ulterioare ale eval() văd tot ce ați definit anterior.
console.log și elementele înrudite adaugă date într-un buffer al motorului în loc să scrie la stdout. Citiți-l cu getConsoleOutput() (un șir, cu liniile unite prin \n) sau cu getConsoleLines() (o listă de linii) și goliți-l cu clearConsole().
Pornirea la rece este rapidă: la început se instalează doar nucleul necesar pentru încărcarea specificațiilor (Object, Function, Array, Promise, Symbol, BigInt, RegExp și tipurile de erori). Map, TypedArray, Temporal, Intl, URL, fetch, pachetele Web Platform / Fetch complete și toate celelalte se materializează transparent la prima citire. Transmiteți new Engine(eager: true) pentru a instala toate elementele încorporate la creare — util pentru instrumentele de conformitate care inspectează descriptorii înainte de a-i citi; rar este necesar la încorporarea obișnuită.
Apelați funcții JS din PHP
$engine->eval('function add(a, b) { return a + b; }');
echo $engine->call('add', 2, 3);
// 5
call() caută funcția după nume pe obiectul global și o invocă folosind argumentele PHP furnizate (convertite automat în valori JS).
Expuneți o funcție apelabilă PHP ca funcție 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")');
Închiderile PHP sunt împachetate ca funcții JS. Argumentele lor sunt convertite din JS în PHP la apel, iar valorile returnate sunt convertite înapoi din PHP în JS.
Obiecte PHP
Obiectele PHP nu pot fi partajate cu JavaScript încă. setGlobal() cu un obiect simplu PHP asociază undefined (urmărit în problema #18). Transmiteți datele ca tablouri, care sunt copiate în JS, și expuneți comportamentul prin închideri:
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
Consultați Obiecte partajate pentru detalii și alte soluții alternative.
Limite de resurse
$engine = new Engine();
$engine->setLimit('maxLoopIterations', 100_000);
maxLoopIterations (valoarea implicită 100,000) este singura limită care poate fi ajustată în timpul rulării, iar în prezent se aplică doar codului executat de interpretul care parcurge arborele. Buclele executate de VM-ul de bytecode (corpurile funcțiilor și majoritatea codului de nivel superior) nu sunt contorizate, astfel că limita singură nu oprește while (true) {} (problema #19). Când se atinge limita, eroarea poate fi prinsă în JS; dacă nu este prinsă, ajunge la PHP drept Phasis\Exceptions\InternalError, o subclasă a lui Phasis\Exceptions\RuntimeError.
Motorul are și o limită fixă de 4,096 cadre pentru stiva de apeluri (și o protecție la 8,192 cadre pentru închiderile transpuse); depășirea acesteia generează „Maximum call stack size exceeded”, tot ca InternalError. Nu există o limită de timp de execuție, așa că rulați codul neîncredere sub PHP cu propriul max_execution_time sau într-un proces separat. Consultați API: setLimit.
Pașii următori
- API — referință cu întreaga suprafață
Phasis\Engine. - Interoperabilitate — reguli de conversie a valorilor, funcții gazdă și obiecte PHP.
- Compatibilitate — ce este implementat și acoperirea test262.
- Parser și Formator — parsați și formatați JavaScript fără a-l executa.
- Avansat — arhitectură, VM de bytecode, teste de performanță și testare oracle.