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.
API Index — Namespaces, Packages, Reports, Indices

RequestContext
in package

Per-request state container. Lives on Coroutine::getContext() in coroutine mode (recommended default) so each request gets isolated state freed automatically when the coroutine ends. In legacy superglobals mode it's a process-wide singleton bridging declared properties to PHP's $_GET / $_POST / $_SESSION etc.

Previously named G — that name remains available via class_alias at the bottom of this file for backward compatibility. New code should reference RequestContext.

Table of Contents

Properties

$_error_handler_in_flight  : bool
Re-entry guards: set true while inside the native dispatcher closure so a nested error/exception fired while running the user-supplied callable falls back to PHP's default handler instead of recursing through our handler until the call stack is exhausted.
$_exception_handler_in_flight  : bool
$_ob_floor  : int|null
OB level of the framework's capture buffer for the current include — app buffers ABOVE it keep native ob_end_flush()/ob_flush() semantics (pop into parent); at/below it the overrides stream to the client.
$_session_started  : bool|null
$_streaming  : bool|null
$apacheContext  : ApacheContext|null
$cache_expire  : int|null
$cache_limiter  : string|null
$cgi_backend_override  : array{mode: string, interpreter?: string, address?: string, fcgi_params?: array}|null
Per-request CGI backend override set by the matched route's backend: option in ResponseMiddleware::dispatchRoute(), read by App::include() to pick the dispatch strategy (pool/proc/fork/fcgi + interpreter/ address) for THIS request's includes. null = no override (fall back to App::resolveCgiBackend() / the global App::cgiMode()).
$cookie  : array<string, mixed>
$error_exception  : Throwable|null
$error_handlers_stack  : array<int, array{0: callable, 1: int}>
$error_render_depth  : int
$error_reporting_level  : int|null
$error_status  : int|null
$exception_handlers_stack  : array<int, callable>
$files  : array<string, mixed>
$get  : array<string, mixed>
$ignore_user_abort_state  : int
$memo  : array<string, mixed>
$openswoole_request  : Request|null
$openswoole_response  : Response|null
$post  : array<string, mixed>
$psr_request  : ServerRequestInterface|null
$raw_status_code  : int|null
Raw status-line override (#327): set ONLY by the `header("HTTP/1.1 <code> <reason>")` form, which Apache mod_php forwards verbatim — code AND reason — even for codes outside 100–599. The vendor PSR-7 withStatus() throws on out-of-table codes, so the raw pair rides these side-channel fields and App::emitEffectiveStatus() overrides the wire status at the emit chokepoints. Any later explicit status set (http_response_code(), Status: header, int return) clears them via response_set_status() — last write wins, like mod_php.
$raw_status_reason  : string|null
$request  : array<string, mixed>
$server  : array<string, scalar|null>
$session  : array<string, mixed>
$session_loaded_keys  : array<int, string>
Keys present in $g->session at session-load time. Lets zeal_session_write_close() distinguish an in-request unset() (key was loaded then removed → must be deleted from the store) from a concurrent add (key never loaded here → must be preserved through the merge). #21.
$session_module_name  : string|null
$session_params  : array<string, mixed>
$shutdown_functions  : array<int, array{0: callable, 1: array}>
$status  : int|null
$zealphp_request  : Request|null
$zealphp_response  : Response|null
$instance  : self|null

Methods

__get()  : mixed
Read by reference is only required in superglobals mode, where the proxy must hand back $GLOBALS['_SESSION'] etc. so legacy code that mutates $_SESSION['k'] = $v carries the write through. In coroutine mode (recommended default) all reads go through the typed properties declared above; returning by value avoids the autovivification footgun where &$g->nonexistent would create a property on first read.
__isset()  : bool
ext-zealphp#42 (residual) — keep isset($g->server) truthful when the slot is an unset-and-proxied superglobal alias. PHP only consults __isset for inaccessible/uninitialized properties; without it, isset($g->server) reported FALSE in coroutine-legacy and under the #346 Apache bridge even though __get returned the fully-populated $_SERVER. App-level defensive code reacting to that false negative (if (!isset($g->server)) { $g->server = []; }) then wiped the live $GLOBALS['_SERVER'] through __set for the rest of the request.
__set()  : mixed
__set fires for undeclared properties AND for declared typed properties that have been unset() (the slot is "uninitialized" so direct access routes through __set on assignment). In superglobals mode we keep the legacy bridge to $GLOBALS[$key] so pre-coroutine code that stashed values via $g->custom = $val keeps working. In coroutine mode the typed properties are the contract; we re-initialize the declared slot (preserves PHP's type-check via direct property assignment) and reject any other write loudly so typos still surface.
__unset()  : void
For the seven proxied superglobal names this is deliberately a NO-OP, not the symmetric inverse of __set. The framework itself uses unset($g->server) as "detach the typed slot so the __get proxy takes over" — bridgeSuperglobalSlots(), the per-request populate in App::run(), and the session managers all do it. __unset only fires when the slot is ALREADY detached, so re-running any of those paths (bridge re-entry, request 2+ on a reused context) must not escalate into unset($GLOBALS['_SERVER']) and destroy live request state — exactly the class of wipe this fix exists to prevent. Deleting a whole superglobal through $g is not a real use case; code that means it can unset($GLOBALS['_SESSION']) directly (as zeal_session_write_close() does). Legacy CUSTOM keys keep __set symmetry: unset($g->custom) drops $GLOBALS['custom']. Coroutine mode is a no-op — __unset firing at all means the slot is already uninitialized, and unset() is idempotent by contract.
forget()  : void
Discard the memoized value for $key in this request. The next once() call with the same key will recompute.
get()  : mixed
has()  : bool
True if once($key, ...) has been computed in this request.
instance()  : self
once()  : mixed
Compute once per request, cache for the rest of the request.
set()  : void
__construct()  : mixed
bridgeSuperglobalSlots()  : void
#346 — Apache/mod_php (and any non-OpenSwoole SAPI) bridge: a DECLARED, default-initialized typed property ("public array $server = []") is "set", so reads resolve from the empty slot and the __get superglobals proxy NEVER runs — $g->server / $g->get / $g->request were always [] under plain Apache even though the SAPI populated $_SERVER/$_GET correctly. Unsetting the request-input slots makes reads AND writes route through __get/__set, which proxy to $GLOBALS['_SERVER'] etc. by reference — the same live-alias contract the ZealPHP server's OnRequest populate establishes per request (there this unset is simply idempotent; under Apache it is the only place that can establish the bridge, because no ZealPHP request lifecycle ever runs). Applied to the process-wide singleton at construction when App::$superglobals is on.

Properties

$_error_handler_in_flight

Re-entry guards: set true while inside the native dispatcher closure so a nested error/exception fired while running the user-supplied callable falls back to PHP's default handler instead of recursing through our handler until the call stack is exhausted.

public bool $_error_handler_in_flight = false

$_exception_handler_in_flight

public bool $_exception_handler_in_flight = false

$_ob_floor

OB level of the framework's capture buffer for the current include — app buffers ABOVE it keep native ob_end_flush()/ob_flush() semantics (pop into parent); at/below it the overrides stream to the client.

public int|null $_ob_floor = null

Set/restored by App::executeFile().

$cgi_backend_override

Per-request CGI backend override set by the matched route's backend: option in ResponseMiddleware::dispatchRoute(), read by App::include() to pick the dispatch strategy (pool/proc/fork/fcgi + interpreter/ address) for THIS request's includes. null = no override (fall back to App::resolveCgiBackend() / the global App::cgiMode()).

public array{mode: string, interpreter?: string, address?: string, fcgi_params?: array}|null $cgi_backend_override = null

$error_handlers_stack

public array<int, array{0: callable, 1: int}> $error_handlers_stack = []

stack of [callable, levels]

$error_reporting_level

public int|null $error_reporting_level = null

$exception_handlers_stack

public array<int, callable> $exception_handlers_stack = []

stack of callables

$ignore_user_abort_state

public int $ignore_user_abort_state = 0

$openswoole_request

public Request|null $openswoole_request = null

In tests, this slot may hold a mock — see tests/Unit/RestTest.php

$openswoole_response

public Response|null $openswoole_response = null

In tests, this slot may hold a mock — see tests/Unit/RestTest.php

$psr_request

public ServerRequestInterface|null $psr_request = null

The PSR-7 request for the current dispatch; set by ResponseMiddleware::process() so ZealAPI (and other inner layers) can reach the same object the middleware stack used.

$raw_status_code

Raw status-line override (#327): set ONLY by the `header("HTTP/1.1 <code> <reason>")` form, which Apache mod_php forwards verbatim — code AND reason — even for codes outside 100–599. The vendor PSR-7 withStatus() throws on out-of-table codes, so the raw pair rides these side-channel fields and App::emitEffectiveStatus() overrides the wire status at the emit chokepoints. Any later explicit status set (http_response_code(), Status: header, int return) clears them via response_set_status() — last write wins, like mod_php.

public int|null $raw_status_code = null

$session_loaded_keys

Keys present in $g->session at session-load time. Lets zeal_session_write_close() distinguish an in-request unset() (key was loaded then removed → must be deleted from the store) from a concurrent add (key never loaded here → must be preserved through the merge). #21.

public array<int, string> $session_loaded_keys = []

$session_module_name

public string|null $session_module_name = null

$session_params

public array<string, mixed> $session_params = []

$shutdown_functions

public array<int, array{0: callable, 1: array}> $shutdown_functions = []

queue of [callable, args]

$zealphp_request

public Request|null $zealphp_request = null

In tests, this slot may hold a mock — see tests/Unit/RestTest.php

$zealphp_response

public Response|null $zealphp_response = null

In tests, this slot may hold a mock — see tests/Unit/RestTest.php

Methods

__get()

Read by reference is only required in superglobals mode, where the proxy must hand back $GLOBALS['_SESSION'] etc. so legacy code that mutates $_SESSION['k'] = $v carries the write through. In coroutine mode (recommended default) all reads go through the typed properties declared above; returning by value avoids the autovivification footgun where &$g->nonexistent would create a property on first read.

public & __get(string $key) : mixed
Parameters
$key : string

__isset()

ext-zealphp#42 (residual) — keep isset($g->server) truthful when the slot is an unset-and-proxied superglobal alias. PHP only consults __isset for inaccessible/uninitialized properties; without it, isset($g->server) reported FALSE in coroutine-legacy and under the #346 Apache bridge even though __get returned the fully-populated $_SERVER. App-level defensive code reacting to that false negative (if (!isset($g->server)) { $g->server = []; }) then wiped the live $GLOBALS['_SERVER'] through __set for the rest of the request.

public __isset(string $key) : bool

Superglobals mode mirrors isset($_X) for the seven proxied names — Apache parity, so isset($g->session) stays FALSE until session_start creates $_SESSION — and isset($GLOBALS[$key]) for legacy custom keys (symmetric with __get/__set). Coroutine mode reports FALSE: __isset firing at all means the typed slot is uninitialized (or the key undeclared), and zeal_session_status() depends on an unset $g->session reading as inactive.

Parameters
$key : string
Return values
bool

__set()

__set fires for undeclared properties AND for declared typed properties that have been unset() (the slot is "uninitialized" so direct access routes through __set on assignment). In superglobals mode we keep the legacy bridge to $GLOBALS[$key] so pre-coroutine code that stashed values via $g->custom = $val keeps working. In coroutine mode the typed properties are the contract; we re-initialize the declared slot (preserves PHP's type-check via direct property assignment) and reject any other write loudly so typos still surface.

public __set(string $key, mixed $value) : mixed
Parameters
$key : string
$value : mixed

__unset()

For the seven proxied superglobal names this is deliberately a NO-OP, not the symmetric inverse of __set. The framework itself uses unset($g->server) as "detach the typed slot so the __get proxy takes over" — bridgeSuperglobalSlots(), the per-request populate in App::run(), and the session managers all do it. __unset only fires when the slot is ALREADY detached, so re-running any of those paths (bridge re-entry, request 2+ on a reused context) must not escalate into unset($GLOBALS['_SERVER']) and destroy live request state — exactly the class of wipe this fix exists to prevent. Deleting a whole superglobal through $g is not a real use case; code that means it can unset($GLOBALS['_SESSION']) directly (as zeal_session_write_close() does). Legacy CUSTOM keys keep __set symmetry: unset($g->custom) drops $GLOBALS['custom']. Coroutine mode is a no-op — __unset firing at all means the slot is already uninitialized, and unset() is idempotent by contract.

public __unset(string $key) : void
Parameters
$key : string

forget()

Discard the memoized value for $key in this request. The next once() call with the same key will recompute.

public static forget(string $key) : void
Parameters
$key : string

get()

public static get(string $key) : mixed
Parameters
$key : string

has()

True if once($key, ...) has been computed in this request.

public static has(string $key) : bool
Parameters
$key : string
Return values
bool

once()

Compute once per request, cache for the rest of the request.

public static once(string $key, callable $fn) : mixed

Safe alternative to static $cache = [] inside a function. Computes $fn() the first time it's called with $key in this request, caches the result on the per-coroutine RequestContext, returns the cached value on subsequent calls. The cache is freed automatically when the coroutine ends — no state survives to the next request.

Mirrors Laravel 11's once() helper. Use this anywhere you'd reach for static $foo = ... for request-scoped memoization but want to avoid leaking state into worker process memory.

$user = RequestContext::once('current_user', fn() => Auth::loadUser($id));
Parameters
$key : string
$fn : callable

set()

public static set(string $key, mixed $value) : void
Parameters
$key : string
$value : mixed

bridgeSuperglobalSlots()

#346 — Apache/mod_php (and any non-OpenSwoole SAPI) bridge: a DECLARED, default-initialized typed property ("public array $server = []") is "set", so reads resolve from the empty slot and the __get superglobals proxy NEVER runs — $g->server / $g->get / $g->request were always [] under plain Apache even though the SAPI populated $_SERVER/$_GET correctly. Unsetting the request-input slots makes reads AND writes route through __get/__set, which proxy to $GLOBALS['_SERVER'] etc. by reference — the same live-alias contract the ZealPHP server's OnRequest populate establishes per request (there this unset is simply idempotent; under Apache it is the only place that can establish the bridge, because no ZealPHP request lifecycle ever runs). Applied to the process-wide singleton at construction when App::$superglobals is on.

private static bridgeSuperglobalSlots(self $instance) : void
Parameters
$instance : self
On this page