Kom igång
Installation
composer require phasis/phasis
Phasis kräver PHP 8.2 eller senare med tre tillägg, som alla krävs av composer.json:
mbstring— stränghantering.bcmath— BigInt-aritmetik och hantering av heltal med exakt precision.curl— standardtransporten bakomfetch().
mbstring och curl är aktiverade i de flesta byggen. bcmath är det ofta inte: de officiella php-Docker-avbildningarna (php:8.x-cli, php:8.x-fpm) saknar det, och vissa distributioner paketerar det separat (php-bcmath). Utan det vägrar Composer att installera Phasis. Lägg till följande i en Dockerfile som bygger på den officiella avbildningen:
RUN docker-php-ext-install bcmath
Kör php -m för att se vilka tillägg som finns i ditt bygge.
ext-intl är valfritt. Phasis fungerar utan det, men hela familjen Intl.* (Collator, NumberFormat, DateTimeFormat, PluralRules, Locale, DisplayNames, ListFormat, RelativeTimeFormat, Segmenter, DurationFormat) och Temporal-kalendrar som inte följer ISO (hebrew, islamic, japanese, persian osv.) kräver det. Installera det när värdapplikationen exponerar JS som använder lokalanpassad formatering. Även ext-openssl är valfritt: AES-CBC-, AES-CTR- och AES-GCM-kryptering med crypto.subtle kräver det, medan hashberäkningar och HMAC fungerar utan det.
Inget exec, ingen FFI, inget Node.js, inget anpassat inbyggt tillägg.
Kör ett skript från 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 skriver ut det som skriptet skriver med console.log; det skriver inte ut värdet av det sista uttrycket.
Se referensen för CLI CLI för den fullständiga uppsättningen kommandon.
Bädda in i 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
Instansen Engine behåller sitt globala tillstånd mellan anrop, så efterföljande anrop till eval() ser allt du har definierat tidigare.
console.log och dess motsvarigheter lägger till i en buffert i motorn i stället för att skriva till stdout. Läs den med getConsoleOutput() (en sträng, med rader sammanfogade med \n) eller getConsoleLines() (en lista med rader), och töm den med clearConsole().
Kallstarten är snabb: endast kärnan som behövs för att läsa in specifikationen (Object, Function, Array, Promise, Symbol, BigInt, RegExp och feltyperna) installeras från början. Map, TypedArray, Temporal, Intl, URL, fetch, de fullständiga Web Platform- och Fetch-paketen samt allt annat materialiseras transparent vid första läsningen. Skicka med new Engine(eager: true) för att installera alla inbyggda delar vid konstruktion — användbart för konformitetsverktyg som granskar deskriptorer innan de läses; behövs sällan vid normal inbäddning.
Anropa JS-funktioner från PHP
$engine->eval('function add(a, b) { return a + b; }');
echo $engine->call('add', 2, 3);
// 5
call() letar upp funktionen med namn på det globala objektet och anropar den med de angivna PHP-argumenten (som automatiskt konverteras till JS-värden).
Gör en anropbar PHP till en JS-funktion
$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-slutningar omsluts som JS-funktioner. Deras argument konverteras från JS till PHP vid anrop, och returvärdena konverteras tillbaka från PHP till JS.
PHP-objekt
PHP-objekt kan ännu inte delas med JavaScript. setGlobal() med ett vanligt PHP-objekt binder undefined (spåras i issue #18). Skicka data som arrayer, vilka kopieras till JS, och exponera beteende som slutningar:
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
Se Delade objekt för information och andra lösningar.
Resursgränser
$engine = new Engine();
$engine->setLimit('maxLoopIterations', 100_000);
maxLoopIterations (standardvärde 100,000) är den enda gräns som kan justeras under körning, och i dag gäller den bara kod som körs av den trädvandrande tolken. Loopar som körs av bytekod-VM:en (funktionskroppar och det mesta av koden på toppnivå) räknas inte, så enbart en gräns stoppar inte while (true) {} (issue #19). När gränsen nås kan felet fångas i JS; om det inte fångas når det PHP som Phasis\Exceptions\InternalError, en underklass till Phasis\Exceptions\RuntimeError.
Motorn har också en fast gräns på 4,096 anropsstackramar (och en spärr vid 8,192 ramar för transpilerade slutningar); om den överskrids kastas "Maximum call stack size exceeded", återigen som InternalError. Det finns ingen tidsgräns, så kör otillförlitlig kod under PHPs egen max_execution_time eller i en separat process. Se API: setLimit.
Nästa steg
- API-referens — hela
Phasis\Engine-gränssnittet. - Samverkan — regler för värdekonvertering, värdfunktioner och PHP-objekt.
- Kompatibilitet — vad som är implementerat och test262-täckning.
- Parser och Formaterare — parsa och formatera JavaScript utan att köra koden.
- Avancerat — arkitektur, bytekod-VM, riktmärken och orakletestning.