Source Transformation
The server intercepts include/require at the stream-wrapper level. Before PHP compiles each file, token_get_all() scans for calls to the 28 shimmed functions and rewrites them to their Q_WebServer_Compat::_* equivalents. The transformed source is cached per file (keyed by mtime) so the tokenization happens only once.
The result: header('Content-Type: application/json') in your code becomes Q_WebServer_Compat::_header('Content-Type: application/json') at runtime. Your code doesn’t change. The server handles the rewrite transparently.
This is the userland equivalent of switch_global_context(), a function proposed for PHP 9 that would do global-state isolation at the engine level. Until that lands, source transformation + snapshot restore gets the same result.
All 28 shimmed functions
| Category | Functions | What the shim does |
|---|---|---|
| Response headers | header, setcookie, setrawcookie, http_response_code, headers_sent, headers_list, header_remove | Captures headers for the server to send (CLI SAPI discards native header()) |
| Sessions | session_start, session_write_close, session_regenerate_id, session_destroy, session_status | File-based sessions with proper locking per worker |
| Uploads | move_uploaded_file, is_uploaded_file | Validates against the server’s tracked temp paths |
| PHP config | ini_get, ini_set, set_time_limit | Tracked per request, restored between requests |
| Request info | getallheaders, apache_request_headers | Returns headers from the parsed HTTP request |
| Lifecycle | register_shutdown_function | Collected per request, fired at request end, cleared |
| Error handling | set_error_handler, set_exception_handler, restore_error_handler, restore_exception_handler | Tracked per request, restored to boot state |
| Autoloaders | spl_autoload_register, spl_autoload_unregister | Request autoloaders removed between requests, boot stack preserved |
| Environment | putenv | Tracked per request, restored to boot values |
What gets reset between requests
Persistent workers handle thousands of requests. Between each one, a Reflection-based snapshot restores all static properties in 0.03ms (cached ReflectionProperty handles). Plus:
Full reset table
| State | How |
|---|---|
| Static class properties | Snapshot restore via cached ReflectionProperty |
| $_GET, $_POST, $_COOKIE, $_FILES, $_SERVER | Re-populated from the request |
| $_SESSION | session_write_close() + cleared |
| Output buffers | ob_end_clean() to level 0 |
| Custom globals | Snapshot + restore |
| Shutdown functions | Collected, fired, cleared (shimmed) |
| Error/exception handlers | Restored to boot state (shimmed) |
| Autoloaders | Request autoloaders removed (shimmed) |
| ini values | Restored to boot values (shimmed) |
| Environment variables | Restored to boot values (shimmed) |
| Response headers | Cleared + http_response_code(200) |
.htaccess Support
The server reads .htaccess files and supports common directives: RewriteRule, RewriteCond, DirectoryIndex, ErrorDocument, Options, and Header. WordPress, Laravel, and Symfony routing works out of the box.
Response Headers
Your PHP sends special headers with Q_Response::header(). The server acts on them — caching responses, streaming files, invalidating components. These are the same headers nginx understands (like X-Accel-Redirect) plus new ones for component-level caching.
Cache-Control — server-side response cache
// Cached for 5 minutes — server serves without running PHP
Q_Response::header('Cache-Control: public, max-age=300');
echo renderFeed();
// Cache but revalidate — server generates ETag from response body
Q_Response::header('Cache-Control: public, max-age=0, must-revalidate');
echo renderProfile($userId);
// Never cache (admin pages, user-specific data)
Q_Response::header('Cache-Control: no-store');
echo renderDashboard();
X-Accel-Redirect — access-controlled file serving
PHP checks access, then tells the server to stream the file. The client never sees the real path.
myproject/
web/
download.php ← checks access
files/
private/
doc.pdf ← served only through download.php
// web/download.php
session_start();
if (!$_SESSION['user_id'] || !userCanAccess($_SESSION['user_id'], $_GET['id'])) {
http_response_code(403);
exit;
}
Q_Response::header("X-Accel-Redirect: /files/private/{$_GET['id']}");
Q_Response::header('Content-Disposition: attachment; filename="document.pdf"');
X-Cache-Tree — per-component cache invalidation
Traditional caching invalidates entire pages. X-Cache-Tree lets you cache a page composed of components (feed, sidebar, header) and invalidate them independently.
// Tell the server which components this page uses
Q_Response::header('X-Cache-Tree: ' . json_encode([
'feed' => md5($feedHtml),
'sidebar' => md5($sidebarHtml),
'header' => md5($headerHtml),
]));
// Tell the server what data each component depends on
Q_Response::header('X-Cache-Deps: ' . json_encode([
'feed' => ['posts:' . $communityId],
'sidebar' => ['members:' . $communityId],
]));
// When data changes, invalidate just the affected components
Q_Response::header('X-Cache-Invalidate: ' . json_encode([
'posts:' . $communityId
]));
// Only the feed re-renders. Sidebar stays cached.
All response headers at a glance
| Header | What it does |
|---|---|
Cache-Control | Server caches the response, serves without running PHP |
X-Accel-Redirect | Server streams a file after PHP checks access |
X-Cache-Tree | Registers page components with content hashes |
X-Cache-Deps | Maps components to data dependency keys |
X-Cache-Invalidate | Marks dependency keys as stale |
X-Cache-Stale | Invalidates cached pages containing named components |
X-Accel-Buffering: no | Disable buffering (auto-detected for SSE) |
Configuration
{
"Q": {
"webserver": {
"forkPerRequest": false,
"skipSourceCodeTransform": false
}
}
}
Both options default to false — the fast, safe path. Set "forkPerRequest": true for process-per-request isolation (shared hosting with untrusted plugins). Set "skipSourceCodeTransform": true only if your code already uses the Q APIs directly.