Phasis
Рушій JavaScript, написаний на PHP: ES2024+ із проходженням усіх 50 506 тестів test262 (знімок від 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На практиці
Один об’єкт рушія запускає JavaScript у процесі PHP і передає значення в обох напрямках.
Запускайте JavaScript з PHP
Phasis\Engine обчислює рядки та файли з кодом у процесі PHP, який його викликає. setGlobal() перетворює масиви PHP на масиви й об’єкти JavaScript, а замикання PHP обгортає у функції.
Вивід console.log буферизується в рушії, а зчитується через getConsoleOutput(). Форматувальнику на другій вкладці рушій зовсім не потрібен.
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
Проходить усі 50 506 тестів зі знімка набору перевірок на відповідність ECMAScript від 2025-05-05, усі категорії, без пропусків.
Сучасний ECMAScript
Класи, async/await, генератори, Symbol, BigInt, Proxy, Temporal, TypedArray та Intl з ext-intl. Повна підтримка ES2024+.
API вебплатформи
Вбудовано URL, TextEncoder і TextDecoder, atob і btoa, structuredClone, performance та DOMException.
fetch + потоки
Справжні HTTP через JS за допомогою ext-curl. Повний Fetch API з AbortSignal, Blob, FormData та WHATWG Streams.
Тести вебплатформи
Імпортовані підтести WPT для fetch, headers, blob, abort, streams і encoding проходять, за винятком дозволеного списку випадків brotli та SharedArrayBuffer.
Взаємодія PHP ↔ JS
setGlobal() надає замикання PHP як функції JS, а call() запускає функції JS з PHP. Значення конвертуються на межі; масиви копіюються.
Можна вбудовувати
Один клас Engine: eval, execFile, setGlobal, call. Додавайте його до будь-якого застосунку PHP або CLI.
Обмеження стека викликів
Фіксований стек викликів на 4 096 кадрів зупиняє неконтрольовану рекурсію помилкою InternalError, яку можна перехопити.
CLI
bin/phasis запускає файл або фрагмент -e і виводить AST за допомогою --ast. У клоні репозиторію bin/test262 запускає набір тестів на відповідність.
Оракул Node.js
Сценарії перевіряються за Node.js (V8). WPT і бібліотечний набір тестів запускаються в CI під час надсилання змін, що стосуються рушія; повний прогін test262 запускається на вимогу, востаннє — 2026-06-09.
Справжні бібліотеки
Перевіряються пакети npm 204, зокрема acorn, mustache.js, lodash і marked; їхній вивід побайтно збігається з Node.
Чому Phasis
Без процесу Node.js
Phasis виконує лексичний аналіз, розбирає та запускає JavaScript безпосередньо в PHP, без виклику Node через exec() і без FFI. Встановлюється як пакет Composer.
fetch під контролем хоста
fetch() за замовчуванням використовує ext-curl. setFetchTransport() дає змогу замінити транспорт, наприклад на макет для тестів, а setFetchPolicy() може відхилити або переписати кожен запит перед надсиланням.
Таймери на віртуальному часі
eval() спорожнює чергу циклу подій перед поверненням, а годинник переходить до наступного активного таймера замість очікування. Хости з тривалим часом роботи можуть виконувати цикл у реальному часі через tickEventLoop().
Парсер і форматувальник у комплекті
Парсер повертає типізоване AST PHP, яке експортується в ESTree. Форматувальник повторно виводить JavaScript, JSON і CSS з компонуванням, сумісним із prettier.
Порівняно з
Інші способи запускати або обробляти JavaScript із застосунку PHP.
| Інструмент | Переваги | Відмінність від Phasis |
|---|---|---|
| Node.js | ПеревагиNode.js запускає JavaScript у рушії V8 і знаходить пакети в каталогах node_modules, тож залежності npm завантажуються за назвою. | Відмінність від PhasisВиклик Node з PHP додає друге середовище виконання та межу підпроцесу. Phasis працює всередині PHP, але його завантажувач модулів не має node_modules і не може знаходити експорти package.json, тож спочатку потрібно зібрати пакети npm, наприклад за допомогою esbuild. |
| V8Js | ПеревагиV8Js вбудовує рушій V8 від Google у PHP і запускає JavaScript у пісочниці, для якої можна встановити обмеження часу та пам’яті. | Відмінність від PhasisV8Js — це скомпільоване розширення, якому потрібна бібліотека libv8. Phasis — звичайний PHP від Composer; у документації вказано, що на коді з обмеженням диспетчеризації він працює приблизно в 100 разів повільніше за V8. |
| Peast | ПеревагиPeast розбирає JavaScript у AST формату ESTree на PHP 5.4 і новіших версіях та додає обхід дерева, запити селекторів і рендеринг. | Відмінність від PhasisДля Phasis потрібна PHP 8.2. Його парсер повертає типізовані незмінні об’єкти вузлів з експортом ESTree; цей самий пакет також може виконувати код. |
| Prettier | ПеревагиPrettier форматує JavaScript, TypeScript, JSX, CSS, HTML, Markdown, YAML та інші формати й встановлюється з npm. | Відмінність від PhasisФорматувальник Phasis підтримує лише JavaScript, JSON і CSS. Його параметри відповідають 3.x prettier, а працює він у PHP без Node.js. |
Запитання та відповіді
Короткі відповіді з документації Phasis.
Для Composer потрібна PHP 8.2 або новіша версія з bcmath, curl і mbstring. Для API Intl та календарів, відмінних від ISO Temporal, рекомендується intl, а для AES-шифрування в crypto.subtle — openssl.
Phasis — інтерпретатор з обходом дерева та віртуальною машиною байткоду, а не JIT. У документації вказано, що на коді з обмеженням диспетчеризації він працює приблизно в 100 разів повільніше за V8; за їхніми словами, для вбудованих сценаріїв на кшталт правил перевірки ця межа рідко має значення.
За назвою — ні. Завантажувач модулів знаходить імпорти у файловій системі, вимагає явних розширень файлів і не підтримує node_modules чи експорти package.json, тож спершу зберіть пакет за допомогою такого інструмента, як esbuild, і передайте Phasis зібраний файл.
Ні. window, document і дерево DOM не входять до комплекту, як і глобальні об’єкти Node, зокрема process, Buffer і require().
Він буферизується в рушії, а не виводиться на екран. Зчитуйте його через getConsoleOutput() або getConsoleLines(), а очищуйте буфер за допомогою clearConsole().
Неперехоплений виняток передається як Phasis\Exceptions\JsThrowable. Помилки, які сам рушій створює, наприклад під час читання властивості null, передаються як Phasis\Exceptions\TypeError, ReferenceError або RangeError; усі вони успадковують Phasis\Exceptions\RuntimeError. Некоректний вихідний код спричиняє Phasis\Parser\ParseError — Phasis\Exceptions\SyntaxError із зазначенням рядка, стовпця та зміщення.
Почніть із посібника з початку роботи, а потім прочитайте довідник API про хуки fetch і цикл подій.