Alpha ZealPHP is early-stage and under active development. APIs may change between minor versions until v1.0. Feedback and bug reports welcome on GitHub.

HTTP Protocol Features

ZealPHP implements the full HTTP/1.1 feature set: conditional requests, content negotiation, proper method handling, and CORS.

FeatureStatusHow
HEAD method✅ Auto-mappedResponseMiddleware runs GET handler, strips body, adds Content-Length
OPTIONS method✅ Built-inReturns 204 + Allow header with all methods registered for that URI
ETag / 304✅ MiddlewareETagMiddleware generates W/"md5", returns 304 on If-None-Match hit
Gzip compression✅ OpenSwoolehttp_compression handles bodies when Accept-Encoding includes gzip
CORS✅ MiddlewareCorsMiddleware handles preflight + adds headers to every response
Redirects 301/302/307/308✅ Built-in$response->redirect($url, $status)
Cookie SameSite✅ Built-insetcookie($name, $value, ..., $samesite)
HTTP/2⚙️ ConfigurePass 'enable_http2' => true to $app->run() (requires TLS)
Range requests✅ MiddlewareRangeMiddleware handles single + multi-range (RFC 7233); $response->sendFile() for zero-copy file serving
HEAD HEAD — headers only, body stripped
// Register GET route — HEAD works automatically:
$app->route('/http/head-test', function() {
    header('X-Custom-Header: zealphp');
    echo str_repeat('x', 2048);  // 2KB body
});

// curl -I https://php.zeal.ninja/http/head-test
// → Content-Length: 2048 (no body)
// → X-Custom-Header: zealphp
LIVE OUTPUT Click Run →
OPTIONS OPTIONS — Allow header for URI
$app->route('/http/options-test', ['methods' => ['GET','POST','PUT']], fn() => '');

// curl -X OPTIONS https://php.zeal.ninja/http/options-test -v
// → HTTP/1.1 204 No Content
// → Allow: OPTIONS, GET, HEAD, POST, PUT
LIVE OUTPUT Click Run →
GET Redirects — 301/302/307/308
// $response->redirect() sets Location + status
$app->route('/http/redirect/{code}', function($code, $response) {
    $response->redirect('/http/redirect-target', (int)$code);
});

// Auto-302 on Location header:
header('Location: https://example.com');
// ZealPHP detects Location: → sets status 302 automatically
LIVE OUTPUT Click Run →
GET Range — 206 Partial Content
// Any buffered response supports Range via RangeMiddleware:
$app->route('/http/range-test', function() {
    echo str_repeat('abcdefghij', 100);  // 1000 bytes
});

// curl -H 'Range: bytes=0-9' https://php.zeal.ninja/http/range-test
// → HTTP/1.1 206 Partial Content
// → Content-Range: bytes 0-9/1000
// → abcdefghij
LIVE OUTPUT Click Run →
GET sendFile() — zero-copy file download
// Serve files with zero-copy + automatic Range support:
$app->route('/download/{file}', function($file, $response) {
    $path = "/var/data/{$file}";
    $response->sendFile($path, $file);
});

// curl https://php.zeal.ninja/http/sendfile-test
// → Content-Type: text/css
// → Accept-Ranges: bytes
//
// curl -H 'Range: bytes=0-99' https://php.zeal.ninja/http/sendfile-test
// → HTTP/1.1 206 Partial Content
LIVE OUTPUT Click Run →

Apache + mod_php Parity

ZealPHP overrides 53 PHP built-in functions via ext-zealphp so that code written for Apache + mod_php or nginx + PHP-FPM runs unchanged on a long-lived OpenSwoole server. It populates request state per coroutine and ships middleware mirroring the common Apache/nginx directives. Every remaining gap is listed below with its workaround — nothing is left undocumented.

Function overrides (53 functions)

At server boot, ext-zealphp intercepts these functions and routes them to per-request objects. Your code calls header() — the framework writes to the correct OpenSwoole response. No code changes needed.

Response

FunctionStatusNotes
header() / header_remove() / headers_list() / headers_sent()✅ NativeWrite to the per-request response; Location: auto-sets 302
http_response_code()✅ NativeLast code wins, like mod_php
setcookie() / setrawcookie()✅ NativeFull 7-arg form incl. SameSite
header_register_callback()✅ NativeFires once before buffered headers flush

Sessions (18 functions)

FunctionStatusNotes
session_start() / session_id() / session_status() / session_name()✅ NativePer-coroutine, file-backed
session_write_close() / session_commit() / session_abort()✅ Native
session_destroy() / session_unset() / session_regenerate_id()✅ Native
session_get_cookie_params() / session_set_cookie_params()✅ Native
session_cache_limiter() / session_cache_expire()✅ Native
session_encode() / session_decode() / session_save_path() / session_module_name()✅ Native

Output control

FunctionStatusNotes
flush() / ob_flush() / ob_end_flush()✅ NativeFlush to OpenSwoole response
ob_implicit_flush()✅ Native
output_add_rewrite_var() / output_reset_rewrite_vars()✅ Native

Error handling

FunctionStatusNotes
set_error_handler() / restore_error_handler()✅ NativePer-coroutine stack
set_exception_handler() / restore_exception_handler()✅ NativePer-coroutine stack
register_shutdown_function()✅ NativePer-request
error_log() / error_reporting()✅ NativeRoutes into framework log (debug.log)

Exec family

FunctionStatusNotes
shell_exec() / backtick operator✅ NativeCoroutine-safe via App::exec()
exec() / system() / passthru()✅ NativeYields in coroutine, blocking fallback outside

Other

FunctionStatusNotes
phpinfo()✅ NativeZealPHP-branded dark theme with runtime diagnostics
filter_input() / filter_input_array()✅ NativeRead INPUT_GET/POST/COOKIE/SERVER/ENV from request state
is_uploaded_file() / move_uploaded_file()✅ NativeValidate against the request's uploaded set
apache_request_headers() / getallheaders() / apache_response_headers()✅ NativeApache-only shims registered globally
apache_setenv() / apache_getenv() / apache_note()✅ Native
php_sapi_name()⚙️ Opt-inDefault returns "cli"; set App::sapiName('apache2handler') for legacy parity
set_time_limit() / ignore_user_abort()✅ Native
connection_status() / connection_aborted()✅ Native

Superglobals

SuperglobalStatusNotes
$_GET / $_POST / $_COOKIE / $_FILES / $_REQUEST✅ NativePopulated per request. Per-coroutine safe with ext-zealphp.
$_SERVER✅ NativeIncludes PHP_SELF, SCRIPT_NAME, SCRIPT_FILENAME
$_SESSION✅ NativePer-coroutine via ext-zealphp + overridden session_start()

Recommended: use $g->get / $g->session (zero overhead, always safe). With ext-zealphp, $_GET / $_SESSION are also per-coroutine safe — saved/restored on every yield/resume via OpenSwoole scheduler hooks (the superglobal-snapshot isolation stage, S1).

$_SERVER keys

Key(s)StatusSource
REQUEST_METHOD / REQUEST_URI / SERVER_PROTOCOL / QUERY_STRING✅ NativeOpenSwoole request
REMOTE_ADDR / REMOTE_PORT / SERVER_PORT✅ NativeOpenSwoole request
REQUEST_TIME / REQUEST_TIME_FLOAT✅ NativeOpenSwoole request
HTTP_* headers✅ NativeTranscribed from request headers
DOCUMENT_ROOT / SCRIPT_NAME / SCRIPT_FILENAME / PHP_SELF / SERVER_SOFTWARE / SERVER_NAME✅ NativeBuilt per request (mod_php convention)
GATEWAY_INTERFACE / REQUEST_SCHEME / HTTPS✅ NativeAdded by ZealPHP; scheme from HTTPS/X-Forwarded-Proto/port 443

Apache directives → middleware & config

Apache / nginxStatusZealPHP
RewriteRule . /index.php [L]✅ NativeApp::setFallback()
mod_headers / mod_expires / AddCharset / AddType / ForceType✅ MiddlewareHeader / Expires / Charset / MimeType middleware
<FilesMatch> Cache-Control / mod_substitute✅ MiddlewareCacheControl / BodyRewrite middleware
AuthType Basic + AuthUserFile / Allow,Deny / Require ip✅ MiddlewareBasicAuth / IpAccess middleware
limit_req / limit_conn (nginx) / server_name vhosts✅ MiddlewareRateLimit / ConcurrencyLimit / HostRouter middleware
Redirect / RedirectMatch (mod_alias)✅ MiddlewareRedirectMiddleware — declarative prefix + regex redirects
SetEnvIf / BrowserMatch (mod_setenvif)✅ MiddlewareSetEnvIfMiddleware — set request env vars on attribute/regex match
RequestHeader (mod_headers)✅ MiddlewareRequestHeaderMiddleware — set/append/unset inbound headers ($g->server)
<Location> / <LocationMatch> / <FilesMatch>✅ MiddlewareScopedMiddleware — apply any middleware only to matching paths
MergeSlashes (core / nginx)✅ MiddlewareMergeSlashesMiddleware — collapse // in the path before routing
client_max_body_size (nginx) / LimitRequestBody✅ MiddlewareBodySizeLimitMiddleware — 413 when Content-Length exceeds the cap
valid_referers (nginx)✅ MiddlewareRefererMiddleware — 403 hotlink protection
return (nginx)✅ MiddlewareReturnMiddleware — fixed status / redirect / body; pair with ScopedMiddleware
mod_deflate✅ MiddlewareCompressionMiddleware (or OpenSwoole native http_compression)
ScriptAlias / Options +ExecCGI✅ NativeApp::cgiScriptAlias() / App::registerCgiBackend()
ErrorDocument✅ NativeApp::instance()->setErrorHandler()
ServerTokens / ServerSignature✅ ConfigApp::serverTokens() controls/omits the X-Powered-By header
FileETag✅ ConfigApp::fileETag(false) disables ETag/304 (FileETag None)
DocumentRoot / TraceEnable / ServerAdmin / ServerName / LimitRequest* / CustomLog✅ ConfigApp::* fluent setters (set before App::init())
Trusted proxies / X-Forwarded-For✅ ConfigApp::$trusted_proxies + App::clientIp()

php.ini directives

DirectiveStatusNotes
max_execution_time🚫 By designset_time_limit() is a no-op — use OpenSwoole coroutine timeouts
auto_prepend_file / auto_append_file🚫 By designNo per-request boot hook — include at your entry point / fallback handler
post_max_size / upload_max_filesize⚠️ GapNot enforced per directive — OpenSwoole's package_max_length is the real cap; middleware planned
default_mimetype✅ MiddlewareCharsetMiddleware applies App::$default_mimetype (default text/html) to untyped responses
max_input_vars⚠️ GapNot enforced (default 1000 rarely hit)

exit() / die()

CallPHP 8.4+PHP 8.3
exit() / exit(0)✅ Worker survives, 200 response⚠️ Kills worker (max_request respawns)
die("message")✅ Worker survives, message as body⚠️ Kills worker
exit(404)✅ Worker survives, HTTP 404⚠️ Kills worker

On PHP 8.3, use throw new \ZealPHP\HaltException() instead of exit(). On PHP 8.4+, exit()/die() throw \ExitException which ZealPHP catches and treats as a clean halt. The exit-hook isolation stage (S12, App::hookExit()) routes exit()/die() through \ZealPHP\HaltException so the worker survives.

Known gaps

GapWhyWorkaround
.htaccess file parsingNo runtime file watcherUse middleware — all major directives have equivalents above
PHP_SAPI constantConstants cannot be redefined at runtimeUse php_sapi_name() function (overridden, configurable)
exit()/die() on PHP 8.3Language constructs, not functionsthrow new HaltException() or upgrade to PHP 8.4+ (the S12 exit-hook stage)
define() persists across requestsLong-running process — constants are permanent once definedprocessIsolation(true) for define-heavy apps (WordPress/Drupal), or coroutine-legacy's opt-in constants stage (S10, App::defineIsolation(true))
Static class properties persistClass entries survive between requestsmax_request recycles workers; avoid mutable static state. Coroutine-legacy isolates class statics per coroutine (S5b) and resets them to the boot template per request (S11c).
getenv()/putenv() for CGI varsNot request-scopedRead from $g->server / $_SERVER; coroutine-legacy isolates the environment per coroutine (the S9h process-settings sub-stage)
mail()Relies on system sendmailConfigurable transport planned
Every async PHP framework shares the last four gaps. They are inherent to the long-running process model, not ZealPHP-specific. ext-zealphp's 53-function override + per-coroutine superglobal isolation is unique to ZealPHP — no other framework intercepts this many built-ins at the C level.