Phasis
محرك JavaScript مكتوب بلغة PHP: يحقق اجتياز جميع اختبارات test262 البالغ عددها 50,506 لدعم ES2024+ (لقطة 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+.
واجهات برمجة منصة الويب
تتوفر 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.
مكتبات حقيقية
تُختبر 204 حزمة npm، منها acorn وmustache.js وlodash وmarked، مع تطابق المخرجات بايتًا ببايت مع Node.
لماذا Phasis؟
لا توجد عملية Node.js
يحلّل Phasis المعجم ويحلّل وينفّذ JavaScript بنفسه في PHP، من دون استدعاء exec() إلى Node أو 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 فقط. تطابق خياراته prettier 3.x ويعمل في PHP من دون Node.js. |
الأسئلة والأجوبة
إجابات موجزة من توثيق Phasis.
يتطلب Composer الإصدار PHP 8.2 أو أحدث مع bcmath وcurl وmbstring. يُنصح بـ intl لواجهات Intl وتقويمات Temporal غير التابعة لـ ISO، وبـ openssl لتشفير AES في crypto.subtle.
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 وحلقة الأحداث.