Guida introduttiva
Installazione
composer require phasis/phasis
Phasis richiede PHP 8.2 o versioni successive e tre estensioni, tutte necessarie per composer.json:
mbstring— gestione delle stringhe.bcmath— operazioni aritmetiche su BigInt e gestione dei numeri con precisione intera.curl— il trasporto predefinito usato dafetch().
mbstring e curl sono abilitate nella maggior parte delle build. Spesso bcmath non lo è: le immagini ufficiali php Docker (php:8.x-cli, php:8.x-fpm) non la includono e alcune distribuzioni la forniscono separatamente (php-bcmath). Senza questa estensione, Composer non si installa Phasis. In un Dockerfile basato sull’immagine ufficiale, aggiungete:
RUN docker-php-ext-install bcmath
Eseguite php -m per vedere quali estensioni include la vostra build.
ext-intl è facoltativo. Phasis funziona anche senza, ma richiedono questa estensione l’intera famiglia Intl.* (Collator, NumberFormat, DateTimeFormat, PluralRules, Locale, DisplayNames, ListFormat, RelativeTimeFormat, Segmenter, DurationFormat) e i calendari Temporal non ISO (hebrew, islamic, japanese, persian ecc.). Installatela se l’applicazione host espone JS che usano una formattazione basata sulle impostazioni locali. Anche ext-openssl è facoltativo: la crittografia AES-CBC, AES-CTR e AES-GCM di crypto.subtle lo richiede, mentre digest e HMAC funzionano senza.
Niente exec, niente FFI, niente Node.js, nessuna estensione nativa personalizzata.
Eseguire uno script dalla riga di comando 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 stampa ciò che lo script scrive con console.log; non stampa il valore dell’ultima espressione.
Per l’elenco completo dei comandi, consultate la documentazione di riferimento di CLI.
Incorporare 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
L’istanza Engine mantiene lo stato globale tra una chiamata e l’altra, quindi le chiamate successive a eval() vedono tutto ciò che è stato definito in precedenza.
console.log e le funzioni correlate aggiungono i contenuti a un buffer del motore invece di scriverli su stdout. Leggeteli con getConsoleOutput() (un’unica stringa, con le righe unite da \n) o getConsoleLines() (un elenco di righe) e svuotate il buffer con clearConsole().
L’avvio a freddo è rapido: all’inizio viene installato solo il nucleo necessario per caricare le specifiche (Object, Function, Array, Promise, Symbol, BigInt, RegExp e i tipi di errore). Map, TypedArray, Temporal, Intl, URL, fetch, i pacchetti Web Platform / Fetch completi e tutto il resto vengono resi disponibili in modo trasparente alla prima lettura. Passate new Engine(eager: true) per installare tutti i componenti integrati durante la costruzione: è utile per gli strumenti di conformità che esaminano i descrittori prima di leggerli, ma raramente serve per l’incorporamento normale.
Chiamare funzioni JS da PHP
$engine->eval('function add(a, b) { return a + b; }');
echo $engine->call('add', 2, 3);
// 5
call() cerca la funzione per nome nell’oggetto globale e la invoca con gli argomenti PHP forniti (convertiti automaticamente in valori JS).
Esporre una funzione richiamabile PHP come funzione 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")');
Le closure PHP vengono racchiuse in funzioni JS. Durante la chiamata, i loro argomenti vengono convertiti da JS a PHP e i valori restituiti vengono riconvertiti da PHP a JS.
Oggetti PHP
Gli oggetti PHP non possono ancora essere condivisi con JavaScript. setGlobal() con un oggetto PHP semplice associa undefined (tracciato in segnalazione n. 18). Passate i dati come array, che vengono copiati in JS, ed esponete il comportamento tramite closure:
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
Per i dettagli e altre soluzioni alternative, consultate Oggetti condivisi.
Limiti delle risorse
$engine = new Engine();
$engine->setLimit('maxLoopIterations', 100_000);
maxLoopIterations (valore predefinito 100,000) è l’unico limite configurabile durante l’esecuzione e al momento si applica solo al codice eseguito dall’interprete che attraversa l’albero. I cicli eseguiti dalla VM bytecode (corpi delle funzioni e la maggior parte del codice di livello superiore) non vengono conteggiati, quindi il solo limite non arresta while (true) {} (segnalazione n. 19). Quando si raggiunge il limite, l’errore può essere intercettato in JS; se non viene intercettato, arriva a PHP come Phasis\Exceptions\InternalError, sottoclasse di Phasis\Exceptions\RuntimeError.
Il motore ha anche un limite fisso di 4,096 frame per lo stack delle chiamate (e una protezione di 8,192 frame per le closure transpiled); superarlo genera «Maximum call stack size exceeded», sempre come InternalError. Non esiste un limite di tempo reale: eseguite il codice non attendibile con il limite max_execution_time di PHP o in un processo separato. Consultate API: impostaLimite.
Passaggi successivi
- API documentazione di riferimento — superficie
Phasis\Enginecompleta. - Interoperabilità — regole di conversione dei valori, funzioni host, oggetti PHP.
- Compatibilità — funzionalità implementate, copertura test262.
- Parser e Formattatore — analizzare e formattare JavaScript senza eseguirlo.
- Avanzate — architettura, VM bytecode, benchmark, test con oracle.