Compatibility

28 functions shimmed. Unmodified WordPress, Laravel, Symfony, and Drupal.

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
CategoryFunctionsWhat the shim does
Response headersheader, setcookie, setrawcookie, http_response_code, headers_sent, headers_list, header_removeCaptures headers for the server to send (CLI SAPI discards native header())
Sessionssession_start, session_write_close, session_regenerate_id, session_destroy, session_statusFile-based sessions with proper locking per worker
Uploadsmove_uploaded_file, is_uploaded_fileValidates against the server’s tracked temp paths
PHP configini_get, ini_set, set_time_limitTracked per request, restored between requests
Request infogetallheaders, apache_request_headersReturns headers from the parsed HTTP request
Lifecycleregister_shutdown_functionCollected per request, fired at request end, cleared
Error handlingset_error_handler, set_exception_handler, restore_error_handler, restore_exception_handlerTracked per request, restored to boot state
Autoloadersspl_autoload_register, spl_autoload_unregisterRequest autoloaders removed between requests, boot stack preserved
EnvironmentputenvTracked 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
StateHow
Static class propertiesSnapshot restore via cached ReflectionProperty
$_GET, $_POST, $_COOKIE, $_FILES, $_SERVERRe-populated from the request
$_SESSIONsession_write_close() + cleared
Output buffersob_end_clean() to level 0
Custom globalsSnapshot + restore
Shutdown functionsCollected, fired, cleared (shimmed)
Error/exception handlersRestored to boot state (shimmed)
AutoloadersRequest autoloaders removed (shimmed)
ini valuesRestored to boot values (shimmed)
Environment variablesRestored to boot values (shimmed)
Response headersCleared + 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
HeaderWhat it does
Cache-ControlServer caches the response, serves without running PHP
X-Accel-RedirectServer streams a file after PHP checks access
X-Cache-TreeRegisters page components with content hashes
X-Cache-DepsMaps components to data dependency keys
X-Cache-InvalidateMarks dependency keys as stale
X-Cache-StaleInvalidates cached pages containing named components
X-Accel-Buffering: noDisable 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.

Full reset documentation on GitHub →