Začetek
Namestitev
composer require phasis/phasis
Phasis potrebuje PHP 8.2 ali novejšo različico s tremi razširitvami, ki jih vse zahteva composer.json:
mbstring— obdelava nizov.bcmath— aritmetika BigInt in obdelava števil z natančnostjo celih števil.curl— privzeti transport zafetch().
mbstring in curl sta omogočena v večini različic. bcmath pa pogosto ni: uradne slike php Docker (php:8.x-cli, php:8.x-fpm) ga ne vključujejo, nekatere distribucije pa ga ponujajo ločeno (php-bcmath). Brez njega Composer zavrne namestitev Phasis. V Dockerfile, ki temelji na uradni sliki, dodajte:
RUN docker-php-ext-install bcmath
Zaženite php -m, da preverite, katere razširitve so na voljo v vaši različici.
ext-intl ni obvezen. Phasis deluje tudi brez njega, vendar ga potrebuje celotna družina Intl.* (Collator, NumberFormat, DateTimeFormat, PluralRules, Locale, DisplayNames, ListFormat, RelativeTimeFormat, Segmenter, DurationFormat) ter koledarji, ki niso ISO (hebrew, islamic, japanese, persian itd.) Temporal. Namestite ga, kadar gostiteljska aplikacija omogoča JS, ki uporabljajo oblikovanje, prilagojeno področne nastavitve. Tudi ext-openssl ni obvezen: za šifriranje AES-CBC, AES-CTR in AES-GCM ga potrebuje crypto.subtle, medtem ko zgoščevalne funkcije in HMAC delujejo brez njega.
Brez exec, brez FFI, brez Node.js, brez lastne izvorne razširitve.
Zaženite skript iz 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 izpiše, kar skript zapiše z console.log; vrednosti zadnjega izraza ne izpiše.
Za celoten nabor ukazov glejte CLI.
Vdelava v 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
Instanca Engine med klici ohrani svoje globalno stanje, zato poznejši klici eval() vidijo vse, kar ste določili prej.
console.log in sorodne funkcije dodajajo vsebino v medpomnilnik v mehanizmu, namesto da bi jo zapisovale v stdout. Preberite ga z getConsoleOutput() (en niz, vrstice so združene z \n) ali getConsoleLines() (seznam vrstic), izpraznite pa z clearConsole().
Hladen zagon je poceni: vnaprej se namesti samo jedro, ki nosi specifikacije (Object, Function, Array, Promise, Symbol, BigInt, RegExp, vrste napak). Map, TypedArray, Temporal, Intl, URL, fetch, celotni paketi Web Platform in Fetch ter vse drugo se pregledno ustvarijo ob prvem branju. Podajte new Engine(eager: true), da se ob izdelavi namestijo vse vgrajene komponente — uporabno za orodja za preverjanje skladnosti, ki pregledajo deskriptorje, preden jih preberejo; pri običajni vdelavi je to redko potrebno.
Klicanje funkcij JS iz PHP
$engine->eval('function add(a, b) { return a + b; }');
echo $engine->call('add', 2, 3);
// 5
call() poišče funkcijo po imenu v globalnem objektu in jo pokliče s podanimi argumenti PHP (samodejno pretvorjenimi v vrednosti JS).
Izpostavitev klicljive funkcije PHP kot funkcije 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")');
Zaprtja PHP so ovita kot funkcije JS. Ob klicu se njihovi argumenti pretvorijo iz JS v PHP, vrnjene vrednosti pa nazaj iz PHP v JS.
Objekti PHP
Objektov PHP še ni mogoče deliti z JavaScript. setGlobal() z navadnim objektom PHP veže undefined (spremljano v težavi #18). Podatke posredujte kot polja, ki se kopirajo v JS, obnašanje pa izpostavite kot zaprtja:
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
Podrobnosti in druge rešitve najdete v razdelku Skupni objekti.
Omejitve virov
$engine = new Engine();
$engine->setLimit('maxLoopIterations', 100_000);
maxLoopIterations (privzeta vrednost 100,000) je edina omejitev, ki jo je mogoče nastavljati med izvajanjem, trenutno pa velja samo za kodo, ki jo izvaja tolmač s sprehodom po drevesu. Zanke, ki jih izvaja bajtkodna navidezna naprava (telesa funkcij in večina vrhnjenivojske kode), se ne štejejo, zato sama omejitev ne ustavi while (true) {} (težava #19). Ko je omejitev dosežena, lahko napako prestrežete v JS; če je ne prestrežete, doseže PHP kot Phasis\Exceptions\InternalError, podrazred Phasis\Exceptions\RuntimeError.
Mehanizem ima tudi fiksno omejitev klicnega sklada na 4,096 okvirjev (in varovalo na 8,192 okvirjev za transpajlirana zaprtja); ob prekoračitvi se sproži »Maximum call stack size exceeded«, znova kot InternalError. Časovna omejitev ne obstaja, zato nezaupanja vredno kodo izvajajte v lastnem PHP max_execution_time ali v ločenem procesu. Glejte API: setLimit.
Naslednji koraki
- API — celoten nabor funkcij
Phasis\Engine. - Sodelovanje — pravila pretvorbe vrednosti, gostiteljske funkcije, objekti PHP.
- Združljivost — kaj je implementirano, pokritost z test262.
- Razčlenjevalnik in Oblikovalnik — razčlenjevanje in oblikovanje JavaScript brez izvajanja.
- Napredno — arhitektura, bajtkodna navidezna naprava, primerjalna merila in testiranje z orakljem.