Back to blog

Week in review: 25 May 2026

A recap of what shipped during the week of 25 May. Three items, all on the bootstrap and runtime side: multiple new SAPIs (including the embedded -S server modelled on PHP's built-in mode), a TypeScript port of the bootstrap module that streamlines the start-up sequence to mirror PHP (extension loading, ini reading, the lot), and the final piece of the centralized-storage work that separates per-request execution contexts cleanly.

Multiple SAPIs and the embedded server

The runtime now ships with multiple SAPIs sitting in front of the same execution layer. The default is still the CLI, which behaves as a drop-in for the PHP CLI for scripts and tooling. The FPM-style SAPI for FastCGI front-ends is in place. The new addition this week is the embedded HTTP server, modelled on PHP's built-in -S mode, that lets you spin a transpiled application up locally with a single command and serve it directly from the runtime.

The embedded server is for development and for lightweight production cases where the cost of a separate web server is not worth it. It runs in-process, keeps connections cheap, and integrates with the request lifecycle the same way a full SAPI does (bootstrap, request, response, teardown). For a longer view of how all of these SAPIs fit together, and the async and multi-threaded ones still in development, see the request-model post from a couple of weeks back.

Bootstrap on TypeScript

The bootstrap module has been ported from JavaScript to TypeScript. The motivation was less about types and more about the opportunity to streamline the start-up sequence. The PHP bootstrap is a precise piece of choreography (read php.ini, load extensions in the order the ini declares, register them with the engine, expose their constants and functions, call MINIT per extension, run the request bootstrap, call RINIT, then hand control to the script), and Pext's bootstrap had grown a number of small divergences over time. The port was the opportunity to bring it back into alignment.

The user-facing result is that extension loading is now declared in an ini-style configuration the runtime reads at start-up, in the same order, with the same semantics PHP applies. Adding a new extension is a matter of declaring it in the configuration and shipping the module; the bootstrap picks it up the next time the process starts. Reading and writing ini values from user code goes through the same mechanism the engine uses internally, so directives that PHP applications already set (memory limits, error reporting, default encodings) take effect the way they would under PHP.

Centralized storage, separated contexts

The last piece of this week's work finishes the separation between execution and storage that has been in flight for a few weeks. Each request now runs against its own execution context, which holds the per-request mutable state (locals, current scope, current exception, current output buffer level). All shared mutable state (the autoload registry, the function and class tables, the constant table, the session store, the configured ini values) lives in the centralized storage layer behind a single interface, and the execution context talks to that interface to read and write.

On the default synchronous SAPI this is invisible: one request runs at a time, the context is reset between requests, nothing changes. The win is what it unlocks. With execution cleanly separated from storage, the async/await SAPI and the multi-threaded SAPI become tractable, because the only place isolation has to be enforced is the storage interface, and the interface knows which context is asking. The two SAPIs are next.