Phasis
Un motor JavaScript escrito en PHP: supera ES2024+ y todas las pruebas test262 50,506 (instantánea del 2025-05-05), además de URL, codificación, structuredClone, fetch y Streams.
composer require phasis/phasisÚltima versión 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.jsEn la práctica
Un único objeto de motor ejecuta JavaScript dentro del proceso PHP y transfiere valores en ambas direcciones.
Ejecutar JavaScript desde PHP
Phasis\Engine evalúa cadenas de código fuente y archivos en el proceso PHP que lo invoca. setGlobal() convierte los arrays de PHP en arrays y objetos de JavaScript, y envuelve los cierres de PHP como funciones.
La salida de console.log se almacena en el motor y se recupera con getConsoleOutput(). El formateador de la segunda pestaña no necesita ningún motor.
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
Sin exec(), sin FFI ni Node. Requiere PHP 8.2+ con ext-curl, ext-bcmath y ext-mbstring; la CI ejecuta PHP 8.4 y 8.5.
100% test262
Supera todas las pruebas 50.506 de la suite de conformidad de ECMAScript en su instantánea del 2025-05-05, todas las categorías y sin omisiones.
ECMAScript moderno
Clases, async/await, generadores, Symbol, BigInt, Proxy, Temporal, TypedArray e Intl con ext-intl. ES2024+ completo.
API de plataforma web
URL, TextEncoder y TextDecoder, atob y btoa, structuredClone, performance y DOMException vienen integrados.
fetch + Streams
HTTP reales desde JS mediante ext-curl. Fetch API completo con AbortSignal, Blob, FormData y WHATWG Streams.
Pruebas de la plataforma web
Las subpruebas importadas de WPT para fetch, headers, blob, abort, streams y encoding pasan, salvo una lista permitida de casos de brotli y SharedArrayBuffer.
Interoperabilidad PHP ↔ JS
setGlobal() expone los cierres de PHP como funciones de JS, y call() ejecuta funciones de JS desde PHP. Los valores se convierten en el límite; los arrays se copian.
Integrable
Una sola clase Engine: eval, execFile, setGlobal, call. Incorpórela a cualquier aplicación PHP o CLI.
Límite de pila de llamadas
Una pila de llamadas fija de 4096 marcos detiene la recursión descontrolada con un InternalError capturable.
CLI
bin/phasis ejecuta un archivo o fragmento -e e imprime el AST con --ast. En un clon del repositorio, bin/test262 ejecuta la suite de conformidad.
Oráculo Node.js
Los escenarios se comprueban con Node.js (V8). WPT y la suite de la biblioteca se ejecutan en CI con las inserciones que afectan al motor; la ejecución completa de test262 se realiza bajo demanda, la última vez el 2026-06-09.
Bibliotecas reales
Se prueban 204 paquetes npm, entre ellos acorn, mustache.js, lodash y marked, con resultados idénticos byte por byte a Node.
Por qué Phasis
Sin proceso Node.js
Phasis analiza léxica y sintácticamente, y ejecuta JavaScript dentro de PHP, sin llamadas exec() a Node ni FFI. Se instala como paquete Composer.
fetch bajo control del host
fetch() usa ext-curl de forma predeterminada. setFetchTransport() sustituye el transporte, por ejemplo por una simulación en las pruebas, y setFetchPolicy() puede denegar o reescribir cada solicitud antes de enviarla.
Temporizadores con tiempo virtual
eval() vacía la cola de eventos antes de devolver el control, y el reloj avanza hasta el siguiente temporizador pendiente en vez de esperar. Los procesos anfitriones de larga duración pueden controlar el bucle en tiempo real con tickEventLoop().
Analizador y formateador incluidos
El analizador devuelve un AST tipado de PHP que se exporta a ESTree. El formateador vuelve a imprimir JavaScript, JSON y CSS con una disposición compatible con prettier.
Comparación con
Otras formas de ejecutar o procesar JavaScript desde una aplicación PHP.
| Herramienta | Ideal para | Diferencia con Phasis |
|---|---|---|
| Node.js | Ideal paraNode.js ejecuta JavaScript en el motor V8 y resuelve paquetes desde carpetas node_modules, así que las dependencias de npm se cargan por nombre. | Diferencia con PhasisInvocar Node desde PHP añade un segundo entorno de ejecución y un límite de subproceso. Phasis se ejecuta dentro de PHP, pero su cargador de módulos no dispone de node_modules ni resuelve exportaciones de package.json, así que primero hay que empaquetar los paquetes npm, por ejemplo con esbuild. |
| V8Js | Ideal paraV8Js integra el motor de Google V8 en PHP y ejecuta JavaScript en un entorno aislado que se puede limitar por tiempo y memoria. | Diferencia con PhasisV8Js es una extensión compilada que necesita la biblioteca libv8. Phasis es PHP puro de Composer, y su documentación lo sitúa aproximadamente a 100 veces el tiempo de ejecución de V8 en código limitado por el despacho. |
| Peast | Ideal paraPeast analiza JavaScript en un AST ESTree con PHP 5.4 y versiones posteriores, y añade recorrido del árbol, consultas de selectores y renderizado. | Diferencia con PhasisPhasis requiere PHP 8.2. Su analizador devuelve objetos de nodo tipados y de solo lectura con exportación a ESTree, y el mismo paquete también puede ejecutar el código. |
| Prettier | Ideal paraPrettier formatea JavaScript, TypeScript, JSX, CSS, HTML, Markdown, YAML y más, y se instala desde npm. | Diferencia con PhasisEl formateador de Phasis solo admite JavaScript, JSON y CSS. Sus opciones siguen las de prettier 3.x y se ejecuta en PHP, sin Node.js. |
Preguntas y respuestas
Respuestas breves de la documentación de Phasis.
Composer requiere PHP 8.2 o posterior con bcmath, curl y mbstring. Se recomienda intl para las API de Intl y los calendarios Temporal que no sean de ISO, y openssl para el cifrado AES en crypto.subtle.
Phasis es un intérprete que recorre el árbol con una máquina virtual de bytecode, no un JIT. La documentación estima que tarda aproximadamente 100 veces más que V8 en código limitado por el despacho; según indica, ese límite rara vez importa en tareas de integración como las reglas de validación.
No por nombre. El cargador de módulos resuelve las importaciones en el sistema de archivos, requiere extensiones de archivo explícitas y no admite node_modules ni exportaciones de package.json; por eso, empaquete el paquete con una herramienta como esbuild y pase el paquete generado a Phasis.
No. No se incluyen window, document ni el árbol DOM, ni tampoco globales de Node como process, Buffer y require().
Se almacena en el motor en vez de imprimirse. Léala con getConsoleOutput() o getConsoleLines(), y vacíe el búfer con clearConsole().
Un throw no capturado se muestra como Phasis\Exceptions\JsThrowable. Los errores que genera el motor, como al leer una propiedad de null, se muestran como Phasis\Exceptions\TypeError, ReferenceError o RangeError, y todos extienden Phasis\Exceptions\RuntimeError. El código fuente no válido genera Phasis\Parser\ParseError, un Phasis\Exceptions\SyntaxError, con la línea, la columna y el desplazamiento.
Empieza con la guía de inicio y luego consulta la referencia de API para los hooks de fetch y el bucle de eventos.