انتقل إلى المحتوى

واجهات Web API

إلى جانب ECMAScript نفسها، يوفر Phasis عدة حزم من واجهات WHATWG / W3C:

  • حزمة منصة الويب — أنواع قيم خالصة، بلا إدخال وإخراج: URL، URLSearchParams، URLPattern، TextEncoder، TextDecoder، atob، btoa، structuredClone، performance، DOMException.
  • حزمة Fetch — HTTP فعلي من JavaScript: fetch، Request، Response، Headers، Body، AbortController، AbortSignal، Blob، File، FormData، EventTarget، Event، ومواصفات WHATWG Streams كاملة (ReadableStream، WritableStream، TransformStream، استراتيجيات الاصطفاف، BYOB)، وCompressionStream / DecompressionStream (gzip / deflate / deflate-raw عبر zlib؛ وbrotli عند تحميل إضافة PECL ‏kjdev/php-ext-brotli)، وnavigator، والاسم البديل العام وفق مواصفات HTML self + reportError.
  • التشفير — crypto.getRandomValues، وcrypto.randomUUID، وتنفيذ SubtleCrypto كامل: digest ‏(SHA-1/256/384/512)، وHMAC، وAES-GCM/CBC/CTR، وRSA-OAEP/PSS/PKCS1، وECDSA ‏(P-256/384/521)، وECDH، وHKDF، وPBKDF2 — بالإضافة إلى استيراد/تصدير JWK. يستند إلى ext-openssl.
  • WebSocket — المُنشئ، ومشفّر إطارات RFC 6455، والأحداث. يستخدم النقل الافتراضي stream_socket_client؛ ويمكن استبداله عبر Engine::setWebSocketTransport() لاستخدام ReactPHP / Ratchet / المحاكاة الوهمية في الاختبارات.
  • XMLHttpRequest — مبني على نقل fetch، لذا تنطبق عليه خطافات السياسة نفسها وإمكانية تبديل النقل.
  • حلقة الأحداث — setTimeout، وsetInterval، وclearTimeout، وclearInterval، وqueueMicrotask. بالإضافة إلى AsyncContext (المرحلة 3)، الذي يمرر القيم عبر حدود المهام الدقيقة / المؤقتات.

جميع الحزم مفعّلة افتراضيًا. بلا أعلام أو إعداد — يعيد typeof fetch القيمة "function" على new Engine() جديد.

المطابقة

تغطي مجموعة اختبارات منصة الويب المستوردة (tests/Wpt/fixtures/) ‏AbortController / AbortSignal، وatob / btoa، وBlob / File، وTextEncoder / TextDecoder، وEventTarget / Event، وFormData، وHeaders، وperformance.now، ومعيار Streams كاملًا (Readable/Writable/Transform + BYOB + الاصطفاف)، وstructuredClone، وURL / URLSearchParams، والواجهة الكاملة لـ crypto / SubtleCrypto (digest وHMAC وAES-GCM/CBC/CTR وRSA-OAEP/PSS/PKCS1 وECDSA وECDH وHKDF وPBKDF2 — بما يشمل آلاف التباديل لاشتقاق البتات من hkdf وpbkdf2)، وآلة حالات WebSocket (التحقق من المُنشئ URL وبروتوكولات الاتصال الفرعية، ودلالات رموز الإغلاق، وsend() للسلاسل / ArrayBuffer / عروض المصفوفات المكتوبة / Blob، وbinaryType، وbufferedAmount، والإغلاق غير المتزامن أثناء الاتصال، وإمكانية أن تكون معالجات الأحداث null)، وشريحة واسعة من Fetch + XMLHttpRequest — آلة حالات responseType، وانتقالات الإلغاء/الإرسال، والرؤوس المسموح بها في CORS، وتحميلات FormData متعددة الأجزاء، وXMLHttpRequestUpload + ProgressEvent، والتحقق من تهيئة Response/Headers، والمُنشئان الثابتان Response.json / Response.error، وإزالة Authorization في سلسلة إعادة التوجيه، وعناوين blob من URL.createObjectURL، واستهلاك مزيج المتن (text / blob / json / formData / arrayBuffer)، وتدفقات tees من Response.clone ذات structuredClone، وإطلاق خطأ شبكة عند استخدام XHR المتزامن. اجتاز كل اختبار فرعي مستورد.

لزيادة التغطية، أضف ملف *.any.js جديدًا إلى tests/Wpt/fixtures/<category>/ — وسيلتقطه المشغّل تلقائيًا. تُدار مجموعتا اختبارات XHR/Fetch بواسطة tests/Wpt/fetch-server.php (محاكي في ملف واحد لمعالجات WPT ‏resources/*.py)؛ وتستخدم مجموعة WebSocket نقلًا خاملًا داخل المشغّل يحاكي الفتح/الصدى/الإغلاق، وهو كافٍ للملفات الاختبارية المستوردة. شغّل المجموعة بنفسك:

سطر الأوامر
bin/wpt                # everything
bin/wpt --category headers --verbose   # one area, per-subtest output
bin/wpt --json         # machine-readable

مثال fetch()

JavaScript
const res = await fetch("https://api.example.com/users", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ name: "alice" }),
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const user = await res.json();

يعمل ذلك مباشرةً. النقل الافتراضي هو PHP ext-curl. تتدفق المتون عبر ReadableStream Phasis الخاص بمواصفات WHATWG الذي يوفره، لذا يعمل for await (const chunk of res.body) أيضًا.

مثال AbortController

JavaScript
const ctrl = new AbortController();
setTimeout(() => ctrl.abort(), 5000);
try {
  const res = await fetch(slowUrl, { signal: ctrl.signal });
} catch (e) {
  if (e.name === "AbortError") {
    // request was cancelled
  }
}

يُنفّذ AbortSignal.timeout(ms) أيضًا (بموعد نهائي كسول — يتحقق عند قراءة aborted بدلًا من إطلاق حدث في منتصف حلقة الأحداث؛ ويعمل مع عمليات نقل fetch التي تستطلع الإشارة أثناء النقل).

عناصر تحكم المُضمّن (جانب PHP)

تتحكم ثلاثة خطافات في سلوك fetch ضمن الصنف Phasis\Engine:

PHP
$engine = new Phasis\Engine();

// 1. Swap the HTTP transport. The callable receives a request descriptor
//    and an optional AbortSignal; returns a response descriptor.
$engine->setFetchTransport(function (array $request, $signal) {
    // ... use Guzzle, Symfony HttpClient, your stub, etc.
    return [
        'status' => 200,
        'statusText' => 'OK',
        'headers' => [['content-type', 'application/json']],
        'body' => '{"data":"value"}',
    ];
});

// 2. Pre-flight policy hook — rewrite, deny, or allow.
$engine->setFetchPolicy(function ($request) {
    $url = $request->get('url')->value;
    if (!str_starts_with($url, 'https://')) {
        throw new \RuntimeException('HTTPS only');
    }
    return null;  // allow as-is; return modified Request to rewrite
});

// 3. Cookie jar. Opt-in. Accepts any object with get(url):string and
//    set(url, header):void methods. JsObject or PHP object both work.
$engine->setCookieJar($myJar);

إذا لم يُستدعَ setFetchTransport()، فسيُنفّذ CurlTransport الافتراضي. وعاء ملفات تعريف الارتباط اختياري صراحةً — لا يوجد وعاء افتراضي ولا تُخزّن ملفات تعريف الارتباط ما لم توفر واحدًا.

مثال Streams

JavaScript
// Producer: ReadableStream that yields numbers 1..5
const rs = new ReadableStream({
  start(controller) {
    for (let i = 1; i <= 5; i++) controller.enqueue(i);
    controller.close();
  },
});

// Consumer: async iteration
for await (const value of rs) {
  console.log(value);  // 1, 2, 3, 4, 5
}

// Pipe through a transform
const ts = new TransformStream({
  transform(chunk, controller) { controller.enqueue(chunk * 2); },
});
const doubled = new ReadableStream({
  start(c) { [1, 2, 3].forEach(n => c.enqueue(n)); c.close(); }
}).pipeThrough(ts);
for await (const v of doubled) console.log(v);  // 2, 4, 6

مواصفات Streams كاملة: ReadableStream، WritableStream، TransformStream، ReadableStreamDefaultController، ReadableByteStreamController، ReadableStreamDefaultReader، ReadableStreamBYOBReader، ReadableStreamBYOBRequest، WritableStreamDefaultController، WritableStreamDefaultWriter، TransformStreamDefaultController، ByteLengthQueuingStrategy، CountQueuingStrategy، بالإضافة إلى pipeTo / pipeThrough / tee.

حلقة الأحداث

توجد setTimeout وsetInterval وclearTimeout وclearInterval وqueueMicrotask على الكائن العام. تُستنفد المهام الدقيقة (معالجات Promise، وqueueMicrotask) بالكامل بين كل مهمة كبرى؛ وتُطلق المؤقتات حسب ترتيب المواعيد النهائية عبر قائمة انتظار خاصة بكل نطاق.

JavaScript
console.log('sync');
queueMicrotask(() => console.log('microtask'));
setTimeout(() => console.log('timer'), 0);
// → sync, microtask, timer

const id = setInterval(() => console.log('tick'), 1000);
// ...later
clearInterval(id);

await new Promise(r => setTimeout(r, 50));   // common "delay" pattern works

تديرها Phasis عبر Engine::runEventLoop()، الذي يستدعي eval() وevalAsModule() تلقائيًا بعد انتهاء الشيفرة المتزامنة. الحلقة تستخدم الوقت الافتراضي افتراضيًا — فلا ينتظر setTimeout(cb, 5000) PHP؛ بل تقدّم الحلقة ساعتها إلى الموعد النهائي كي يكتمل البرنامج بسرعة. أما المُضمّنون الذين يحتاجون إلى الوقت الفعلي فيقودون tickEventLoop() بأنفسهم. راجع API: حلقة الأحداث للتعرف على الواجهة المتاحة للمضيف.

ما لا يتم شحنه

واجهات API المخصصة للمتصفح أو لنموذج بيئة التشغيل هذه خارج النطاق عمدًا — راجع القيود المعروفة للاطلاع على التفاصيل كاملةً:

  • window، وdocument، وشجرة DOM
  • localStorage، وsessionStorage، وIndexedDB
  • Worker، وSharedWorker، وMessageChannel
  • Service Workers، وPush API، والإشعارات، وBroadcastChannel

يمكن للمُضمّن توفير بدائل برمجية لمعظم هذه الواجهات عبر $engine->setGlobal() عند الحاجة.

توسيع مجموعة WPT

إن tests/Wpt/upstream/ عبارة عن سحب جزئي من web-platform-tests/wpt يغطي fetch/، وstreams/، وFileAPI/، وxhr/formdata/، وdom/abort/، وdom/events/. لتوسيع التغطية: انسخ أي ملف *.any.js من upstream/<area>/ إلى tests/Wpt/fixtures/<category>/. سيلتقطه المشغّل في التشغيل التالي؛ وتُسجل حالات الإخفاق عبر bin/wpt --verbose.