Skip to content

Phasis

A JavaScript engine written in PHP: ES2024+ passing all 50,506 test262 tests (2025-05-05 snapshot), plus URL, encoding, structuredClone, fetch and Streams.

Terminal
composer require phasis/phasis

Latest release v0.4.2 MIT PHP 8.2+ with bcmath, curl and mbstring

In practice

One engine object runs JavaScript inside the PHP process and passes values in both directions.

Run JavaScript from PHP

Phasis\Engine evaluates source strings and files in the PHP process that calls it. setGlobal() turns PHP arrays into JavaScript arrays and objects, and wraps PHP closures as functions.

console.log output is buffered on the engine and read back with getConsoleOutput(). The formatter in the second tab needs no engine at all.

Engine
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"

Pure PHP

No exec, no FFI, no Node. Requires PHP 8.2+ with ext-curl, ext-bcmath and ext-mbstring; CI runs PHP 8.4 and 8.5.

100% test262

Passes all 50,506 tests of the ECMAScript conformance suite at its 2025-05-05 snapshot, every category, no skips.

Modern ECMAScript

Classes, async/await, generators, Symbol, BigInt, Proxy, Temporal, TypedArray, and Intl with ext-intl. Full ES2024+.

Web Platform APIs

URL, TextEncoder and TextDecoder, atob and btoa, structuredClone, performance and DOMException are built in.

fetch + Streams

Real HTTP from JS via ext-curl. Full Fetch API with AbortSignal, Blob, FormData, and WHATWG Streams.

Web Platform Tests

Imported WPT subtests for fetch, headers, blob, abort, streams and encoding pass, apart from an allowlist of brotli and SharedArrayBuffer cases.

PHP ↔ JS Interop

setGlobal() exposes PHP closures as JS functions and call() runs JS functions from PHP. Values are converted at the boundary; arrays are copied.

Embeddable

One Engine class: eval, execFile, setGlobal, call. Drop into any PHP app or CLI.

Call Stack Cap

A fixed 4,096-frame call stack stops runaway recursion with a catchable InternalError.

CLI

bin/phasis runs a file or an -e snippet and prints the AST with --ast. In a repository checkout, bin/test262 runs the conformance suite.

Node.js Oracle

Scenarios are checked against Node.js (V8). WPT and the library suite run in CI on pushes that touch the engine; the full test262 pass runs on demand, last on 2026-06-09.

Real Libraries

204 npm packages, among them acorn, mustache.js, lodash and marked, are tested with output byte-equal to Node.

Why Phasis

No Node.js process

Phasis lexes, parses and executes JavaScript in PHP itself, with no exec() call to Node and no FFI. It installs as a Composer package.

fetch under host control

fetch() uses ext-curl by default. setFetchTransport() swaps the transport, for example for a mock in tests, and setFetchPolicy() can deny or rewrite each request before it goes out.

Timers on virtual time

eval() drains the event loop before it returns, and the clock jumps to the next pending timer instead of sleeping. Long-lived hosts can drive the loop in real time with tickEventLoop().

Parser and formatter included

The parser returns a typed PHP AST that exports to ESTree. The formatter reprints JavaScript, JSON and CSS with prettier-compatible layout.

Compared with

Other ways to run or process JavaScript from a PHP application.

Phasis compared with other tools
Node.js Good atNode.js runs JavaScript on the V8 engine and resolves packages from node_modules folders, so npm dependencies load by name. Difference with PhasisCalling Node from PHP adds a second runtime and a subprocess boundary. Phasis runs inside PHP, but its module loader has no node_modules or package.json exports resolution, so npm packages are bundled first, for example with esbuild.
V8Js Good atV8Js embeds Google's V8 engine in PHP and runs JavaScript in a sandbox that can be restricted with a time limit and a memory limit. Difference with PhasisV8Js is a compiled extension that needs the libv8 library. Phasis is plain PHP from Composer, and its docs put it at roughly 100 times the runtime of V8 on dispatch-bound code.
Peast Good atPeast parses JavaScript into an ESTree AST on PHP 5.4 and later, and adds tree traversal, selector queries and rendering. Difference with PhasisPhasis needs PHP 8.2. Its parser returns typed, readonly node objects with an ESTree export, and the same package can also execute the code.
Prettier Good atPrettier formats JavaScript, TypeScript, JSX, CSS, HTML, Markdown, YAML and more, and installs from npm. Difference with PhasisThe Phasis formatter covers JavaScript, JSON and CSS only. Its options mirror prettier 3.x and it runs in PHP, with no Node.js.

Questions and answers

Short answers from the Phasis documentation.

Start with the getting started guide, then read the API reference for the fetch hooks and the event loop.