API Web
Au-delà d'ECMAScript, Phasis fournit plusieurs ensembles d'API WHATWG / W3C à plusieurs niveaux :
- Ensemble Plateforme Web — types de valeurs purs, sans E/S :
URL,URLSearchParams,URLPattern,TextEncoder,TextDecoder,atob,btoa,structuredClone,performance,DOMException. - Ensemble Fetch — véritables HTTP de JavaScript :
fetch,Request,Response,Headers,Body,AbortController,AbortSignal,Blob,File,FormData,EventTarget,Event, Streams WHATWG complets (ReadableStream,WritableStream,TransformStream, stratégies de mise en file d'attente, BYOB),CompressionStream/DecompressionStream(gzip / deflate / deflate-raw via zlib ; brotli si l'extension PECLkjdev/php-ext-brotliest chargée),navigatoret l'alias globalselfde la spécification HTML +reportError. - Crypto —
crypto.getRandomValues,crypto.randomUUIDetSubtleCryptocomplet : digest (SHA-1/256/384/512), HMAC, AES-GCM/CBC/CTR, RSA-OAEP/PSS/PKCS1, ECDSA (P-256/384/521), ECDH, HKDF, PBKDF2 — ainsi que l'import/export JWK. S'appuie surext-openssl. - WebSocket — constructeur, codec de trames RFC 6455, événements. Le transport par défaut utilise
stream_socket_client; il peut être remplacé viaEngine::setWebSocketTransport()pour ReactPHP / Ratchet / les mocks de test. - XMLHttpRequest — repose sur le transport fetch ; les mêmes hooks de stratégie et le transport remplaçable s'appliquent donc.
- Boucle d'événements —
setTimeout,setInterval,clearTimeout,clearInterval,queueMicrotask. En outre,AsyncContext(étape 3) propage les valeurs entre les limites des microtâches et des minuteurs.
Tous les ensembles sont activés par défaut. Aucun indicateur ni configuration : typeof fetch renvoie "function" dans un new Engine() neuf.
Conformité
Le corpus importé de Web Platform Tests (tests/Wpt/fixtures/) couvre AbortController / AbortSignal, atob / btoa, Blob / File, TextEncoder / TextDecoder, EventTarget / Event, FormData, Headers, performance.now, l'intégralité de la norme Streams (Readable/Writable/Transform + BYOB + mise en file d'attente), structuredClone, URL / URLSearchParams, toute la surface crypto / SubtleCrypto (digest, HMAC, AES-GCM/CBC/CTR, RSA-OAEP/PSS/PKCS1, ECDSA, ECDH, HKDF, PBKDF2 — y compris des milliers de permutations derive-bits de hkdf et pbkdf2), la machine à états WebSocket (constructeur URL et validation des sous-protocoles, sémantique des codes de fermeture, send() pour les chaînes / ArrayBuffer / vues de tableaux typés / Blob, binaryType, bufferedAmount, fermeture asynchrone en cours de connexion, nullabilité des gestionnaires d'événements), ainsi qu'une large portion de Fetch + XMLHttpRequest : machine à états responseType, transitions abort/send, en-têtes autorisés par CORS, envois multipart FormData, XMLHttpRequestUpload + ProgressEvent, validation de l'initialisation de Response/Headers, constructeurs statiques Response.json / Response.error, suppression de Authorization lors des redirections, URL blob URL.createObjectURL, consommation du mixin body (text / blob / json / formData / arrayBuffer), flux avec tee Response.clone et structuredClone, et levée d'erreur réseau pour XHR synchrone. Tous les sous-tests importés réussissent.
Pour étendre le corpus, copiez un nouveau *.any.js dans tests/Wpt/fixtures/<category>/ : le lanceur le détectera automatiquement. Les suites XHR/Fetch sont pilotées par tests/Wpt/fetch-server.php (un émulateur monofichier des gestionnaires resources/*.py de WPT) ; la suite WebSocket utilise un transport inerte dans le lanceur qui simule l'ouverture, l'écho et la fermeture, ce qui suffit pour les fixtures importées. Exécutez le corpus vous-même :
bin/wpt # everything
bin/wpt --category headers --verbose # one area, per-subtest output
bin/wpt --json # machine-readable
Exemple fetch()
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();
Cela fonctionne directement. Le transport par défaut est PHP ext-curl. Les corps transitent via les ReadableStream Phasis fournis, selon WHATWG, et for await (const chunk of res.body) fonctionne aussi.
Exemple AbortController
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) est également implémenté (échéance différée : vérifiée à chaque lecture aborted plutôt que par le déclenchement d'un événement au milieu de la boucle d'événements ; fonctionne avec les transports fetch qui vérifient le signal pendant le transfert).
Contrôles de l'intégrateur (côté PHP)
Trois hooks de la classe Phasis\Engine contrôlent le comportement de fetch :
$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);
Si setFetchTransport() n'est pas appelé, le CurlTransport par défaut s'exécute. Le stockage des cookies est facultatif : aucun stockage par défaut, aucun cookie enregistré sans que vous en fournissiez un.
Exemple Streams
// 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
Spécification Streams complète : ReadableStream, WritableStream, TransformStream, ReadableStreamDefaultController, ReadableByteStreamController, ReadableStreamDefaultReader, ReadableStreamBYOBReader, ReadableStreamBYOBRequest, WritableStreamDefaultController, WritableStreamDefaultWriter, TransformStreamDefaultController, ByteLengthQueuingStrategy, CountQueuingStrategy, ainsi que pipeTo / pipeThrough / tee.
Boucle d'événements
setTimeout, setInterval, clearTimeout, clearInterval et queueMicrotask sont sur l'objet global. Les microtâches (gestionnaires Promise, queueMicrotask) sont exécutées jusqu'à épuisement entre chaque macrotâche ; les minuteurs se déclenchent par ordre d'échéance dans une file propre à chaque realm.
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
Pilotée par le Engine::runEventLoop() de Phasis, que eval() et evalAsModule() appellent automatiquement à la fin du code synchrone. Par défaut, la boucle fonctionne en temps virtuel : setTimeout(cb, 5000) n'attend pas PHP ; la boucle avance son horloge jusqu'à l'échéance afin que le programme se termine rapidement. Les intégrateurs en temps réel pilotent eux-mêmes tickEventLoop(). Consultez API : Boucle d'événements pour les fonctionnalités côté hôte.
Fonctionnalités non fournies
Ces API propres aux navigateurs ou liées au modèle d'exécution sont délibérément hors périmètre. Consultez Limitations connues pour en savoir plus :
window,document, l'arbre DOMlocalStorage,sessionStorage,IndexedDBWorker,SharedWorker,MessageChannel- Service Workers, Push API, Notifications, BroadcastChannel
La plupart peuvent être polyfillées par l'intégrateur via $engine->setGlobal() si nécessaire.
Développer le corpus WPT
tests/Wpt/upstream/ est une extraction partielle de web-platform-tests/wpt couvrant fetch/, streams/, FileAPI/, xhr/formdata/, dom/abort/, dom/events/. Pour étendre la couverture, copiez n'importe quel fichier *.any.js depuis upstream/<area>/ vers tests/Wpt/fixtures/<category>/. Le lanceur le détectera à la prochaine exécution ; les échecs seront consignés via bin/wpt --verbose.