Aan de slag
Installeren
composer require phasis/phasis
Phasis vereist PHP 8.2 of hoger met drie extensies, allemaal vereist door composer.json:
mbstring— tekenreeksverwerking.bcmath— BigInt-berekeningen en getallen met gehele precisie.curl— het standaardtransport achterfetch().
mbstring en curl zijn in de meeste builds ingeschakeld. bcmath vaak niet: de officiële php-Docker-images (php:8.x-cli, php:8.x-fpm) bevatten het niet, en sommige distributies leveren het apart (php-bcmath). Zonder dit weigert Composer Phasis te installeren. Voeg in een Dockerfile op basis van de officiële image het volgende toe:
RUN docker-php-ext-install bcmath
Voer php -m uit om te zien welke extensies je build bevat.
ext-intl is optioneel. Phasis werkt zonder, maar de volledige Intl.*-familie (Collator, NumberFormat, DateTimeFormat, PluralRules, Locale, DisplayNames, ListFormat, RelativeTimeFormat, Segmenter, DurationFormat) en de niet-ISO-kalenders van Temporal (hebrew, islamic, japanese, persian enz.) hebben het nodig. Installeer het wanneer de hostapplicatie JS aanbiedt die lokale instellingen gebruikt voor opmaak. ext-openssl is eveneens optioneel: crypto.subtle vereist AES-CBC-, AES-CTR- en AES-GCM-versleuteling, terwijl digests en HMAC zonder werken.
Geen exec, geen FFI, geen Node.js, geen aangepaste native extensie.
Een script uitvoeren vanuit de 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
De CLI toont wat het script met console.log naar buiten schrijft; de waarde van de laatste expressie wordt niet getoond.
Zie de CLI-referentie voor de volledige set opdrachten.
Inbedden in 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
De Engine-instantie bewaart de globale status tussen aanroepen, zodat volgende aanroepen van eval() alles zien wat je eerder hebt gedefinieerd.
console.log en de bijbehorende functies voegen tekst toe aan een buffer op de engine in plaats van naar stdout te schrijven. Lees die met getConsoleOutput() (één tekenreeks, regels samengevoegd met \n) of getConsoleLines() (een lijst met regels) en maak de buffer leeg met clearConsole().
Een koude start is goedkoop: alleen de kern die de specificatie draagt (Object, Function, Array, Promise, Symbol, BigInt, RegExp, de fouttypen) wordt vooraf geïnstalleerd. Map, TypedArray, Temporal, Intl, URL, fetch, de volledige Web Platform / Fetch Packs en al het overige worden transparant geladen bij de eerste uitlezing. Geef new Engine(eager: true) mee om bij het maken alle ingebouwde onderdelen te installeren — handig voor conformiteitstools die descriptors inspecteren voordat ze worden uitgelezen; zelden nodig bij normaal inbedden.
JS-functies aanroepen vanuit PHP
$engine->eval('function add(a, b) { return a + b; }');
echo $engine->call('add', 2, 3);
// 5
call() zoekt de functie op naam op het globale object en roept die aan met de opgegeven argumenten van PHP (automatisch omgezet naar JS-waarden).
Een aanroepbare PHP beschikbaar maken als JS-functie
$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-closures worden verpakt als JS-functies. Bij aanroepen worden argumenten omgezet van JS naar PHP en retourwaarden van PHP terug naar JS.
PHP-objecten
PHP-objecten kunnen nog niet worden gedeeld met JavaScript. setGlobal() met een gewoon PHP-object koppelt undefined (bijgehouden in issue #18). Geef gegevens door als arrays, die naar JS worden gekopieerd, en stel gedrag beschikbaar als closures:
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
Zie Gedeelde objecten voor meer informatie en andere oplossingen.
Resourcelimieten
$engine = new Engine();
$engine->setLimit('maxLoopIterations', 100_000);
maxLoopIterations (standaard 100,000) is de enige limiet die tijdens runtime kan worden ingesteld; momenteel geldt die alleen voor code die door de tree-walking interpreter wordt uitgevoerd. Lussen die de bytecode-VM uitvoert (functieblokken en de meeste code op topniveau) worden niet meegeteld, dus met alleen een limiet wordt while (true) {} niet tegengehouden (issue #19). Wanneer de limiet is bereikt, kan de fout worden opgevangen in JS; als dat niet gebeurt, bereikt de fout PHP als Phasis\Exceptions\InternalError, een subklasse van Phasis\Exceptions\RuntimeError.
De engine heeft ook een vaste limiet van 4,096 frames voor de aanroepstack (en een beveiliging van 8,192 frames voor getranspilede closures); bij overschrijding wordt "Maximum call stack size exceeded" gegooid, opnieuw als InternalError. Er is geen limiet op de wandkloktijd, dus voer niet-vertrouwde code uit onder de eigen max_execution_time van PHP of in een afzonderlijk proces. Zie API: setLimit.
Volgende stappen
- API-referentie — de volledige
Phasis\Engine-interface. - Interoperabiliteit — regels voor waardeconversie, hostfuncties, PHP-objecten.
- Compatibiliteit — wat is geïmplementeerd, test262-dekking.
- Parser en Formatter — JavaScript parsen en opmaken zonder het uit te voeren.
- Geavanceerd — architectuur, bytecode-VM, benchmarks, tests met een orakel.