Phasis
Un motore JavaScript scritto in PHP: ES2024+, tutti i 50.506 test test262 superati (snapshot del 2025-05-05), più URL, codifica, structuredClone, fetch e Streams.
composer require phasis/phasisUltima versione v0.4.2 MIT PHP 8.2+, ext-bcmath, ext-curl, ext-mbstring
./vendor/bin/phasis -e '1 + 2 * 3'
# 7
./vendor/bin/phasis -e '[1, 2, 3].map(x => x * x).join(",")'
# 1,4,9
./vendor/bin/phasis path/to/script.jsNella pratica
Un singolo oggetto engine esegue JavaScript all’interno del processo PHP e consente di passare valori in entrambe le direzioni.
Eseguire JavaScript da PHP
Phasis\Engine valuta stringhe sorgente e file nel processo PHP che lo invoca. setGlobal() converte gli array PHP in array e oggetti JavaScript e avvolge le closure PHP come funzioni.
L’output di console.log viene memorizzato nel motore e letto con getConsoleOutput(). Il formatter nella seconda scheda non richiede alcun motore.
use Phasis\Engine;
$engine = new Engine();
// Evaluate an expression
$engine->eval('1 + 2 * 3'); // 7
// Run a file
$engine->execFile('/path/to/script.js');
// Bridge a PHP value into JS (arrays are copied)
$engine->setGlobal('config', ['debug' => true]);
$engine->eval('config.debug'); // true
// console.log is buffered, not printed
$engine->eval('console.log("hi")');
echo $engine->getConsoleOutput(); // hi
// Expose a PHP closure as a JS function
$engine->setGlobal('greet', fn(string $name) => "Hello, $name");
$engine->eval('greet("world")'); // "Hello, world"use Phasis\Formatter\Formatter;
use Phasis\Formatter\FormatOptions;
$formatted = Formatter::formatSource(
"const a={x:1,y:2};",
'script',
new FormatOptions(useTabs: true, singleQuote: true),
);
// "const a = { x: 1, y: 2 };\n"PHP puro
Nessun exec(), nessun FFI, nessun Node. Richiede PHP 8.2+ con ext-curl, ext-bcmath ed ext-mbstring; la CI usa PHP 8.4 e 8.5.
100% test262
Supera tutti i 50.506 test della suite di conformità ECMAScript nello snapshot del 2025-05-05, in ogni categoria e senza esclusioni.
ECMAScript moderno
Classi, async/await, generatori, Symbol, BigInt, Proxy, Temporal, TypedArray e Intl con ext-intl. ES2024+ completo.
API della piattaforma web
URL, TextEncoder e TextDecoder, atob e btoa, structuredClone, performance e DOMException sono integrati.
fetch + Streams
HTTP reali da JS tramite ext-curl. Fetch API completo con AbortSignal, Blob, FormData e WHATWG Streams.
Test della piattaforma web
I sottotest WPT importati per fetch, headers, blob, abort, streams ed encoding vengono superati, tranne i casi brotli e SharedArrayBuffer inclusi in una allowlist.
Interop PHP ↔ JS
setGlobal() espone le closure PHP come funzioni JS e call() esegue le funzioni JS da PHP. I valori vengono convertiti al confine; gli array vengono copiati.
Integrabile
Una sola classe Engine: eval, execFile, setGlobal, call. Integratela in qualsiasi app PHP o CLI.
Limite dello stack di chiamate
Uno stack di chiamate fisso da 4.096 frame blocca la ricorsione incontrollata con un InternalError intercettabile.
CLI
bin/phasis esegue un file o uno snippet -e e stampa l’AST con --ast. In un checkout del repository, bin/test262 esegue la suite di conformità.
Oracle Node.js
Gli scenari vengono verificati con Node.js (V8). WPT e la suite della libreria vengono eseguiti in CI nei push che modificano il motore; il test completo test262 viene eseguito su richiesta, l’ultima volta il 2026-06-09.
Librerie reali
Vengono testati 204 pacchetti npm, tra cui acorn, mustache.js, lodash e marked, con output identico byte per byte a Node.
Perché Phasis
Nessun processo Node.js
Phasis analizza ed esegue JavaScript direttamente in PHP, senza chiamate exec() a Node e senza FFI. Si installa come pacchetto Composer.
fetch sotto il controllo dell’host
fetch() usa ext-curl per impostazione predefinita. setFetchTransport() sostituisce il trasporto, ad esempio con un mock nei test, mentre setFetchPolicy() può bloccare o riscrivere ogni richiesta prima dell’invio.
Timer basati sul tempo virtuale
eval() svuota il ciclo degli eventi prima di restituire il controllo e fa avanzare l’orologio al timer in attesa successivo invece di attendere. Gli host di lunga durata possono eseguire il ciclo in tempo reale con tickEventLoop().
Parser e formatter inclusi
Il parser restituisce un AST tipizzato PHP che esporta in ESTree. Il formatter ristampa JavaScript, JSON e CSS con un layout compatibile con prettier.
A confronto con
Altri modi per eseguire o elaborare JavaScript da un’applicazione PHP.
| Strumento | Ideale per | Differenze rispetto a Phasis |
|---|---|---|
| Node.js | Ideale perNode.js esegue JavaScript con il motore V8 e risolve i pacchetti dalle cartelle node_modules, così le dipendenze npm vengono caricate per nome. | Differenze rispetto a PhasisChiamare Node da PHP aggiunge un secondo runtime e il confine di un sottoprocesso. Phasis viene eseguito all’interno di PHP, ma il suo loader di moduli non supporta node_modules né la risoluzione delle esportazioni package.json, quindi i pacchetti npm vanno prima inclusi in un bundle, ad esempio con esbuild. |
| V8Js | Ideale perV8Js integra il motore V8 di Google in PHP ed esegue JavaScript in una sandbox con limiti configurabili di tempo e memoria. | Differenze rispetto a PhasisV8Js è un’estensione compilata che richiede la libreria libv8. Phasis è scritto interamente in PHP da Composer e, secondo la documentazione, ha un runtime circa 100 volte quello di V8 con codice limitato dalla distribuzione delle chiamate. |
| Peast | Ideale perPeast analizza JavaScript in un AST ESTree su PHP 5.4 e versioni successive, aggiungendo attraversamento dell’albero, query con selettori e rendering. | Differenze rispetto a PhasisPhasis richiede PHP 8.2. Il suo parser restituisce oggetti nodo tipizzati e di sola lettura, con esportazione ESTree; lo stesso pacchetto può anche eseguire il codice. |
| Prettier | Ideale perPrettier formatta JavaScript, TypeScript, JSX, CSS, HTML, Markdown, YAML e altri formati, e si installa da npm. | Differenze rispetto a PhasisIl formatter Phasis supporta solo JavaScript, JSON e CSS. Le sue opzioni rispecchiano quelle di prettier 3.x ed è eseguito in PHP, senza Node.js. |
Domande e risposte
Risposte brevi dalla documentazione di Phasis.
Composer richiede PHP 8.2 o versioni successive con bcmath, curl e mbstring. intl è consigliato per le API Intl e i calendari Temporal non-ISO, mentre openssl serve per la crittografia AES in crypto.subtle.
Phasis è un interprete che attraversa l’albero con una VM a bytecode, non un JIT. La documentazione indica un runtime circa 100 volte quello di V8 con codice limitato dalla distribuzione delle chiamate; secondo la documentazione, questo limite raramente conta per attività di embedding come le regole di convalida.
Non per nome. Il loader dei moduli risolve gli import sul file system, richiede estensioni di file esplicite e non supporta node_modules né le esportazioni package.json, quindi create un bundle del pacchetto con uno strumento come esbuild e passate il bundle a Phasis.
No. window, document e l’albero DOM non sono inclusi, così come le variabili globali di Node, ad esempio process, Buffer e require().
Viene memorizzato nel motore invece di essere stampato. Leggetelo con getConsoleOutput() o getConsoleLines() e svuotate il buffer con clearConsole().
Un’eccezione non intercettata viene propagata come Phasis\Exceptions\JsThrowable. Gli errori generati dal motore stesso, ad esempio la lettura di una proprietà di null, vengono propagati come Phasis\Exceptions\TypeError, ReferenceError o RangeError, e derivano tutti da Phasis\Exceptions\RuntimeError. Il sorgente non valido genera Phasis\Parser\ParseError, un Phasis\Exceptions\SyntaxError con riga, colonna e offset.
Iniziate con la guida introduttiva, poi consultate il riferimento API per gli hook di fetch e il ciclo degli eventi.