API web
Además de ECMAScript, Phasis incluye varios paquetes escalonados de API WHATWG / W3C:
- Paquete de plataforma web — tipos de valor puros, sin E/S:
URL,URLSearchParams,URLPattern,TextEncoder,TextDecoder,atob,btoa,structuredClone,performance,DOMException. - Paquete Fetch — HTTP real de JavaScript:
fetch,Request,Response,Headers,Body,AbortController,AbortSignal,Blob,File,FormData,EventTarget,Event, Streams completos de WHATWG (ReadableStream,WritableStream,TransformStream, estrategias de encolado, BYOB),CompressionStream/DecompressionStream(gzip / deflate / deflate-raw mediante zlib; brotli cuando se carga la extensión PECLkjdev/php-ext-brotli),navigatory el alias globalselfde la especificación HTML +reportError. - Crypto —
crypto.getRandomValues,crypto.randomUUIDySubtleCryptocompleto: digest (SHA-1/256/384/512), HMAC, AES-GCM/CBC/CTR, RSA-OAEP/PSS/PKCS1, ECDSA (P-256/384/521), ECDH, HKDF y PBKDF2, además de importación y exportación JWK. Funciona conext-openssl. - WebSocket — constructor, códec de tramas RFC 6455 y eventos. El transporte predeterminado usa
stream_socket_client; puede sustituirse medianteEngine::setWebSocketTransport()por ReactPHP / Ratchet / simulaciones en pruebas. - XMLHttpRequest — implementado sobre el transporte fetch, por lo que se aplican los mismos hooks de política y el transporte intercambiable.
- Bucle de eventos —
setTimeout,setInterval,clearTimeout,clearInterval,queueMicrotask. Además,AsyncContext(Stage 3) propaga valores entre los límites de microtareas y temporizadores.
Todos los paquetes están activados de forma predeterminada. Sin indicadores ni configuración: typeof fetch devuelve "function" en un new Engine() nuevo.
Conformidad
El corpus importado de Web Platform Tests (tests/Wpt/fixtures/) abarca AbortController / AbortSignal, atob / btoa, Blob / File, TextEncoder / TextDecoder, EventTarget / Event, FormData, Headers, performance.now, todo el estándar Streams (Readable/Writable/Transform + BYOB + encolado), structuredClone, URL / URLSearchParams, toda la superficie de crypto / SubtleCrypto (digest, HMAC, AES-GCM/CBC/CTR, RSA-OAEP/PSS/PKCS1, ECDSA, ECDH, HKDF, PBKDF2, incluidos miles de casos de permutación de derive-bits de hkdf y pbkdf2), la máquina de estados de WebSocket (constructor URL y validación de subprotocolos, semántica de códigos de cierre, send() para cadenas / ArrayBuffer / vistas de arrays tipados / Blob, binaryType, bufferedAmount, cierre asíncrono durante la conexión, posibilidad de asignar null a los manejadores de eventos) y una amplia parte de Fetch + XMLHttpRequest: máquina de estados de responseType, transiciones de cancelación y envío, encabezados permitidos por CORS, cargas multipart de FormData, XMLHttpRequestUpload + ProgressEvent, validación de inicialización de Response/Headers, constructores estáticos Response.json / Response.error, eliminación de Authorization en cadenas de redirección, URL de blobs URL.createObjectURL, consumo del mixin del cuerpo (text / blob / json / formData / arrayBuffer), streams divididos con structuredClone en Response.clone y errores de red en XHR síncrono. Se aprueban todas las subpruebas importadas.
Para ampliar la cobertura, añade un nuevo *.any.js a tests/Wpt/fixtures/<category>/: el ejecutor lo detecta automáticamente. Las suites XHR/Fetch se ejecutan con tests/Wpt/fetch-server.php (un emulador en un solo archivo de los controladores resources/*.py de WPT); la suite WebSocket usa un transporte inerte dentro del ejecutor que simula apertura, eco y cierre, suficiente para los casos de prueba importados. Ejecuta el corpus tú mismo:
bin/wpt # everything
bin/wpt --category headers --verbose # one area, per-subtest output
bin/wpt --json # machine-readable
Ejemplo de 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();
Funciona directamente. El transporte predeterminado es PHP ext-curl. Los cuerpos se transmiten mediante ReadableStream de WHATWG, que incluye Phasis, por lo que for await (const chunk of res.body) también funciona.
Ejemplo de 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) también está implementado (con un plazo límite diferido: comprueba la lectura aborted, en vez de emitir un evento a mitad del bucle de eventos; funciona con transportes fetch que consultan la señal durante la transferencia).
Controles del entorno anfitrión (lado de PHP)
Hay tres hooks en la clase Phasis\Engine que controlan el comportamiento 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 no se llama a setFetchTransport(), se ejecuta el CurlTransport predeterminado. El almacén de cookies es optativo: no hay uno predeterminado y no se guarda ninguna cookie a menos que proporciones uno.
Ejemplo de 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
Especificación completa de Streams: ReadableStream, WritableStream, TransformStream, ReadableStreamDefaultController, ReadableByteStreamController, ReadableStreamDefaultReader, ReadableStreamBYOBReader, ReadableStreamBYOBRequest, WritableStreamDefaultController, WritableStreamDefaultWriter, TransformStreamDefaultController, ByteLengthQueuingStrategy, CountQueuingStrategy, además de pipeTo / pipeThrough / tee.
Bucle de eventos
setTimeout, setInterval, clearTimeout, clearInterval y queueMicrotask están en el objeto global. Las microtareas (manejadores de Promise, queueMicrotask) se vacían por completo entre cada macrotarea; los temporizadores se ejecutan según el orden de vencimiento mediante una cola por entorno.
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
Controlado por Engine::runEventLoop() de Phasis, que eval() y evalAsModule() invocan automáticamente cuando termina el código síncrono. El bucle usa tiempo virtual de forma predeterminada: setTimeout(cb, 5000) no duerme PHP; el bucle adelanta el reloj hasta el plazo para que el programa termine rápidamente. Los integradores en tiempo real ejecutan tickEventLoop() por su cuenta. Consulta API: bucle de eventos para conocer la superficie del entorno anfitrión.
Lo que no se incluye
Estas API, exclusivas del navegador o propias del modelo de ejecución, quedan deliberadamente fuera del alcance; consulta Limitaciones conocidas para ver todos los detalles:
window,documenty el árbol DOMlocalStorage,sessionStorage,IndexedDBWorker,SharedWorker,MessageChannel- Service Workers, Push API, Notifications, BroadcastChannel
Si hace falta, el integrador puede implementar la mayoría con $engine->setGlobal().
Ampliar el corpus WPT
tests/Wpt/upstream/ es una extracción parcial de web-platform-tests/wpt que cubre fetch/, streams/, FileAPI/, xhr/formdata/, dom/abort/ y dom/events/. Para ampliar la cobertura, copia cualquier archivo *.any.js de upstream/<area>/ a tests/Wpt/fixtures/<category>/. El ejecutor lo detectará la próxima vez que se ejecute; los fallos se registran mediante bin/wpt --verbose.