API Index — Namespaces, Packages, Reports, Indices
ZealAPI
extends REST
in package
File-based API dispatcher.
URL convention — two dispatch modes
Mode 1 — filename match (all methods):
/api/device/list → api/device/list.php defines $list = function(...){...}
/api/device/add → api/device/add.php defines $add = function(...){...}
The closure accepts ALL HTTP methods. The handler reads
$this->get_request_method() if it needs to differentiate.
Mode 2 — per-method dispatch (Next.js App Router style):
/api/users GET → api/users.php defines $get = function(...){...}
/api/users POST → api/users.php defines $post = function(...){...}
Undefined methods return 405 + Allow header. HEAD auto-derives from
$get. OPTIONS lists defined methods automatically.
Resolution: filename match takes priority. If $list exists in list.php,
it wins and any $get/$post in the same file are unreachable (warned).
The variable name MUST match basename($file, '.php'). The closure is
Closure::bind'd to a ZealAPI instance, so inside the handler $this is the
ZealAPI object and you can call $this->paramsExists(), $this->die(), etc.
Parameter injection (by name)
$app → the ZealPHP\App instance
$request → ZealPHP\HTTP\Request
$response → ZealPHP\HTTP\Response
$server → OpenSwoole server
any other → null (or its declared default value)
Error responses
All ZealAPI failures emit JSON with an "error" key and an HTTP status:
400 invalid_module — path component fails the strict regex
400 invalid_request — method name contains slashes/dots/etc
404 method_not_found — file or expected variable name missing
404 undefined_method — handler called $this->X() but X is not a
method on ZealAPI/REST. Response includes
a "hint" and, if a close match is found via
levenshtein, a "did_you_mean" suggestion:
`{ "error": "undefined_method",
"method": "paramExist",
"hint": "...Did you mean $this->paramsExists()?",
"did_you_mean": "paramsExists" }`
Prior to this change, an undefined-method
call inside the handler caused `__call` to
re-invoke the same closure → infinite
recursion. `processApi()` now dispatches the
closure directly, so `__call` is only
reached on real typos.
500 (PHP exception) — uncaught throwable inside the handler;
stack trace logged via elog().
Table of Contents
Properties
- $_allow : array<int|string, mixed>
- $_content_type : string
- $_request : mixed
- $_response : mixed
- $cwd : string|null
- $data : string
- $request : mixed
- $_undefinedMethodError : array<string, mixed>|null
- $api_rpc : Closure|null
- $inFileMiddlewareCache : array<string, array<int, MiddlewareInterface>>
- $is_filename_match : bool
- Which dispatch mode resolved the active handler (#347):
true — filename match (
$listin list.php; serves ALL methods) false — per-method dispatch ($get/$post/…; one closure per method) Drives the null-return contract: a filename-match handler that returns null is an intentional empty 200 (it IS the handler for this method and chose to emit nothing — native-PHP parity), whereas a per-method handler returning null is "this method produced no response" → 404. The two modes are mutually exclusive by design (filename match wins; the framework warns if both are present), so this flag is unambiguous. - $reflectionCache : array<string, array<int, ReflectionParameter>>
Methods
- __call() : mixed
- Catch missing-method calls from inside an API handler closure (e.g. a typo
like
$this->paramExistinstead of$this->paramsExists). - __construct() : mixed
- Construct a
ZealAPIdispatcher bound to the current request/response pair. - die() : void
- failAs() : void
- Shorthand for emitting a 400 JSON error from a caught Throwable.
- get_referer() : mixed
- get_request_method() : mixed
- getUsername() : string|null
- The current user's display name (or null when unauthenticated).
- isAdmin() : bool
- Whether the current user is an admin. Consults the callback
registered with
App::adminChecker()—fn(): bool— or returnsfalseif none. SeeisAuthenticated()for the design. - isAuthenticated() : bool
- Whether the current request is authenticated.
- json() : string
- JSON-encode
$datawithJSON_PRETTY_PRINT. Returns'{}'for non-array input. - paramsExists() : bool
- Return
truewhen all named parameters in$parmsare present in the current request input. - processApi() : mixed
- Dispatch a file-based API request.
- requirePostAuth() : bool
- POST + authenticated guard. Returns false and sends 403 if check fails.
- resolveClubParam() : mixed
- Resolve the canonical "club" identifier from the current request,
accepting either
club(the new name) orgroup(the legacy alias still used by older client code). Returns whatever the request payload carries — typically a string id — or null when neither key is present. - response() : void
- runHandlerWithContract() : ResponseInterface|Generator|null
- Invoke a resolved api handler closure and apply the universal return
contract —
int=HTTP status,array/object=JSON,string=body,string=body. Returns the RAW contract result: a PSR-7Responsefor the buffered cases, the\Generatoritself for an SSR-streaming handler (so the route layer streams it — preserving the pre-feature behaviour), ornullwhen the handler already streamed (via$this->response()/$response->sse()). This is the no-middleware fast path; the in-file$middlewareonion terminal (ApiDispatchHandler) coerces the Generator/ null cases into aResponse. The closure wasClosure::bind'd to$this, so$thisinside it is the ZealAPI instance. - setContentType() : void
- compileInFileMiddleware() : array<int, MiddlewareInterface>
- Resolve an api file's in-file
$middlewarespec (instances + alias strings) to a flat instance list — compiled + memoized per file so a hot endpoint never re-resolves. Reuses the same alias registry + normalizer as route /App::whenmiddleware.
Properties
$_allow
public
array<int|string, mixed>
$_allow
= array()
$_content_type
public
string
$_content_type
= "application/json"
$_request
public
mixed
$_request
= array()
$_response
public
mixed
$_response
= null
$cwd
public
string|null
$cwd
= null
$data
public
string
$data
= ""
$request
public
mixed
$request
= null
$_undefinedMethodError
private
array<string, mixed>|null
$_undefinedMethodError
= null
$api_rpc
private
Closure|null
$api_rpc
$inFileMiddlewareCache
private
static array<string, array<int, MiddlewareInterface>>
$inFileMiddlewareCache
= []
Compiled in-file $middleware chains, memoized per api file.
$is_filename_match
Which dispatch mode resolved the active handler (#347):
true — filename match ($list in list.php; serves ALL methods)
false — per-method dispatch ($get/$post/…; one closure per method)
Drives the null-return contract: a filename-match handler that returns
null is an intentional empty 200 (it IS the handler for this method and
chose to emit nothing — native-PHP parity), whereas a per-method handler
returning null is "this method produced no response" → 404. The two modes
are mutually exclusive by design (filename match wins; the framework warns
if both are present), so this flag is unambiguous.
private
bool
$is_filename_match
= false
$reflectionCache
private
static array<string, array<int, ReflectionParameter>>
$reflectionCache
= []
Methods
__call()
Catch missing-method calls from inside an API handler closure (e.g. a typo
like $this->paramExist instead of $this->paramsExists).
public
__call(string $method, array<int, mixed> $args) : mixed
Previously this proxied to $this->api_rpc — but api_rpc IS the closure
we're currently executing, so the proxy re-invoked it and infinitely
recursed until stack overflow. processApi() now invokes the closure
directly, so __call is only reached on actual typos. Surface the typo
loudly with a "did you mean" hint so developers don't waste time
staring at "method_not_callable" wondering what's wrong.
Parameters
- $method : string
- $args : array<int, mixed>
__construct()
Construct a ZealAPI dispatcher bound to the current request/response pair.
public
__construct(mixed $request, mixed $response, string $cwd) : mixed
Parameters
- $request : mixed
-
The current
ZealPHP\HTTP\Request(or equivalent) object. - $response : mixed
-
The current
ZealPHP\HTTP\Response(or equivalent) object. - $cwd : string
-
Absolute path to the application root (used to resolve
api/files).
die()
public
die(Throwable $e) : void
Parameters
- $e : Throwable
failAs()
Shorthand for emitting a 400 JSON error from a caught Throwable.
public
failAs(Throwable $e) : void
Mirrors the {"error": "<message>"} envelope every other ZealAPI
error path uses, so client code can rely on one error shape.
Parameters
- $e : Throwable
get_referer()
public
get_referer() : mixed
get_request_method()
public
get_request_method() : mixed
getUsername()
The current user's display name (or null when unauthenticated).
public
getUsername() : string|null
Consults the callback registered with App::usernameProvider() —
fn(): ?string — or returns null if none.
Return values
string|nullisAdmin()
Whether the current user is an admin. Consults the callback
registered with App::adminChecker() — fn(): bool — or returns
false if none. See isAuthenticated() for the design.
public
isAdmin() : bool
Return values
boolisAuthenticated()
Whether the current request is authenticated.
public
isAuthenticated() : bool
Consults the callback registered with App::authChecker(). Without
one, returns false (safe fail-closed default). The callback shape is
fn(): bool — typically reads $_SESSION, $g->session, or your
auth system's own state.
See issue #13. Earlier versions hardcoded return false;, breaking
every endpoint guarded by requirePostAuth().
Return values
booljson()
JSON-encode $data with JSON_PRETTY_PRINT. Returns '{}' for non-array input.
public
json(mixed $data) : string
Parameters
- $data : mixed
Return values
stringparamsExists()
Return true when all named parameters in $parms are present in the current request input.
public
paramsExists([array<int, string> $parms = array() ]) : bool
Parameters
- $parms : array<int, string> = array()
-
HTTP parameter names to check.
Return values
boolprocessApi()
Dispatch a file-based API request.
public
processApi(string $module[, string|null $request = null ]) : mixed
Resolves api/{$module}/{$request}.php, selects a handler closure via the
filename-match → per-method priority rules described in the class docblock,
injects named parameters, applies any in-file $middleware, and returns the
result through the universal return contract.
Parameters
- $module : string
-
URL sub-path (e.g.
'device'for/api/device/list) - $request : string|null = null
-
Basename without
.php(e.g.'list')
requirePostAuth()
POST + authenticated guard. Returns false and sends 403 if check fails.
public
requirePostAuth() : bool
Return values
boolresolveClubParam()
Resolve the canonical "club" identifier from the current request,
accepting either club (the new name) or group (the legacy alias
still used by older client code). Returns whatever the request
payload carries — typically a string id — or null when neither key
is present.
public
resolveClubParam() : mixed
response()
public
response(mixed $data[, int|null $status = null ]) : void
Parameters
- $data : mixed
- $status : int|null = null
runHandlerWithContract()
Invoke a resolved api handler closure and apply the universal return
contract — int=HTTP status, array/object=JSON, string=body,
string=body. Returns the RAW contract result: a PSR-7 Response for the
buffered cases, the \Generator itself for an SSR-streaming handler (so
the route layer streams it — preserving the pre-feature behaviour), or
null when the handler already streamed (via $this->response() /
$response->sse()). This is the no-middleware fast path; the in-file
$middleware onion terminal (ApiDispatchHandler) coerces the Generator/
null cases into a Response. The closure was Closure::bind'd to $this,
so $this inside it is the ZealAPI instance.
public
runHandlerWithContract(Closure $handler, array<int, mixed> $invokeArgs) : ResponseInterface|Generator|null
Parameters
- $handler : Closure
- $invokeArgs : array<int, mixed>
Return values
ResponseInterface|Generator|nullsetContentType()
public
setContentType(string $type) : void
Parameters
- $type : string
compileInFileMiddleware()
Resolve an api file's in-file $middleware spec (instances + alias
strings) to a flat instance list — compiled + memoized per file so a hot
endpoint never re-resolves. Reuses the same alias registry + normalizer as
route / App::when middleware.
private
static compileInFileMiddleware(string $realFile, mixed $spec) : array<int, MiddlewareInterface>
Parameters
- $realFile : string
- $spec : mixed