Phasis
JavaScriptエンジンをPHPで実装。ES2024+に対応し、test262の全50,506テストに合格(2025-05-05スナップショット)。URL、エンコーディング、structuredClone、fetch、Streamsも搭載。
composer require phasis/phasis最新リリース 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.js実践例
1つのエンジンオブジェクトで、PHPプロセス内のJavaScriptを実行し、双方向に値を受け渡せます。
PHPからJavaScriptを実行
Phasis\Engineは、呼び出し元のPHPプロセス内でソース文字列やファイルを評価します。setGlobal()はPHP配列をJavaScript配列やオブジェクトに変換し、PHPクロージャを関数としてラップします。
console.logの出力はエンジンにバッファされ、getConsoleOutput()で読み取ります。2つ目のタブにあるフォーマッターは、エンジンをまったく必要としません。
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
exec()もFFIもNodeも不要です。PHP 8.2以降とext-curl、ext-bcmath、ext-mbstringが必要です。CIではPHP 8.4と8.5を実行します。
100% test262
ECMAScript適合性テストスイートの2025-05-05スナップショットに含まれる50,506件すべてに合格。すべてのカテゴリを実行し、スキップはありません。
最新のECMAScript
クラス、async/await、ジェネレーター、Symbol、BigInt、Proxy、Temporal、TypedArray、Intl(ext-intl使用)に対応。ES2024+の全機能を搭載。
Web Platform API
URL、TextEncoderとTextDecoder、atobとbtoa、structuredClone、performance、DOMExceptionを標準搭載。
fetch + ストリーム
ext-curl経由でJSから実際のHTTPにアクセスできます。AbortSignal、Blob、FormData、WHATWG Streamsを含むFetch APIに対応。
ウェブプラットフォームテスト
fetch、headers、blob、abort、streams、encodingのWPTサブテストを取り込み、brotliとSharedArrayBuffer関連の許可リスト対象を除いて合格します。
PHP ↔ JS連携
setGlobal()はPHPクロージャをJS関数として公開し、call()はPHPからJS関数を実行します。値は境界で変換され、配列はコピーされます。
組み込み可能
Engineクラス1つでeval、execFile、setGlobal、callを実行。あらゆるPHPアプリやCLIに組み込めます。
コールスタック上限
固定の4,096フレームのコールスタックにより、無限に続く再帰を捕捉可能なInternalErrorで停止します。
CLI
bin/phasisでファイルまたは-eスニペットを実行し、--astでASTを出力します。リポジトリのチェックアウトでは、bin/test262で適合性テストスイートを実行できます。
Node.jsオラクル
シナリオはNode.js(V8)と照合します。エンジンに関係する変更を含むpushでは、WPTとライブラリスイートをCIで実行します。test262全件の合格確認は任意に実行し、直近では2026-06-09に実施しました。
実際のライブラリ
acorn、mustache.js、lodash、markedなど204個のnpmパッケージをテストし、Nodeとバイト単位で同じ出力になることを確認しています。
Phasisを選ぶ理由
Node.jsプロセス不要
PhasisはPHP自体でJavaScriptを字句解析、構文解析、実行するため、Nodeのexec()呼び出しもFFIもありません。Composerパッケージとしてインストールできます。
ホストがfetchを制御
fetch()はデフォルトでext-curlを使用します。setFetchTransport()で転送方法を切り替えられます(テスト用のモックなど)。setFetchPolicy()を使えば、各リクエストの送信前に拒否または書き換えができます。
仮想時間でタイマーを実行
eval()は戻る前にイベントループを処理し、スリープする代わりに、次に保留中のタイマーまで時計を進めます。長時間稼働するホストでは、tickEventLoop()を使って実時間でループを動かせます。
パーサーとフォーマッターを内蔵
パーサーは型付きPHP ASTを返し、ESTree形式でエクスポートします。フォーマッターはJavaScript、JSON、CSSをprettier互換のレイアウトで再出力します。
との比較
PHPアプリケーションからJavaScriptを実行または処理するその他の方法。
| ツール | 得意なこと | Phasisとの違い |
|---|---|---|
| Node.js | 得意なことNode.jsはV8エンジンでJavaScriptを実行し、node_modulesフォルダーからパッケージを解決するため、npm依存パッケージを名前で読み込めます。 | Phasisとの違いPHPからNodeを呼び出すと、2つ目のランタイムとサブプロセスの境界が加わります。PhasisはPHP内で動作しますが、モジュールローダーにはnode_modulesもpackage.jsonエクスポートの解決機能もありません。そのため、esbuildなどでnpmパッケージを事前にバンドルする必要があります。 |
| V8Js | 得意なことV8JsはGoogleのV8エンジンをPHPに組み込み、JavaScriptを、時間とメモリの上限を設定できるサンドボックスで実行します。 | Phasisとの違いV8Jsはlibv8ライブラリを必要とするコンパイル済み拡張機能です。PhasisはComposerから導入できる純粋なPHPで、ドキュメントによると、ディスパッチ処理が中心のコードではV8の実行時間の約100倍かかります。 |
| Peast | 得意なことPeastはPHP 5.4以降でJavaScriptをESTree ASTに解析し、ツリー走査、セレクタークエリ、レンダリング機能を追加します。 | Phasisとの違いPhasisにはPHP 8.2が必要です。パーサーは型付きの読み取り専用ノードオブジェクトを返し、ESTree形式でエクスポートできます。同じパッケージでコードの実行も可能です。 |
| Prettier | 得意なことPrettierはJavaScript、TypeScript、JSX、CSS、HTML、Markdown、YAMLなどをフォーマットし、npmからインストールできます。 | Phasisとの違いPhasisフォーマッターが対応するのはJavaScript、JSON、CSSのみです。オプションはprettier 3.xに準拠し、PHPで動作します。Node.jsは不要です。 |
質問と回答
Phasisのドキュメントから、簡潔に回答します。
ComposerではPHP 8.2以降とbcmath、curl、mbstringが必要です。Intl APIとISO以外のTemporalカレンダーにはintl、crypto.subtleでのAES暗号化にはopensslを推奨します。
PhasisはバイトコードVMを備えたツリーウォーキング型インタープリターで、JITではありません。ドキュメントによると、ディスパッチ処理が中心のコードではV8の実行時間の約100倍かかります。ただし、検証ルールなどの組み込み用途では、この差が問題になることはまれです。
名前を指定したimportには対応していません。モジュールローダーはファイルシステム上でimport先を解決し、ファイル拡張子を明示する必要があります。node_modulesやpackage.jsonエクスポートにも対応していないため、esbuildなどでパッケージをバンドルし、バンドルをPhasisに渡してください。
ありません。window、document、DOMツリーは含まれず、process、Buffer、require()などのNodeグローバルも含まれません。
出力は表示されず、エンジンにバッファされます。getConsoleOutput()またはgetConsoleLines()で読み取り、clearConsole()でバッファを空にします。
捕捉されなかったthrowはPhasis\Exceptions\JsThrowableとして表面化します。nullのプロパティ読み取りなど、エンジン自体が発生させるエラーはPhasis\Exceptions\TypeError、ReferenceError、RangeErrorとして表面化し、いずれもPhasis\Exceptions\RuntimeErrorを継承します。ソースが無効な場合はPhasis\Parser\ParseErrorがスローされます。これはPhasis\Exceptions\SyntaxErrorの一種で、行、列、オフセットを含みます。
スタートガイドから始め、続けてAPIリファレンスでfetchフックとイベントループを確認してください。