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

ZealAPI extends REST
in package

File-based API dispatcher.

URL convention — two dispatch modes

Mode 1 — filename match (all methods): /api/device/listapi/device/list.php defines $list = function(...){...} /api/device/addapi/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 $requestZealPHP\HTTP\Request $responseZealPHP\HTTP\Response $serverOpenSwoole 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 ($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.
$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->paramExist instead of $this->paramsExists).
__construct()  : mixed
Construct a ZealAPI dispatcher 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 returns false if none. See isAuthenticated() for the design.
isAuthenticated()  : bool
Whether the current request is authenticated.
json()  : string
JSON-encode $data with JSON_PRETTY_PRINT. Returns '{}' for non-array input.
paramsExists()  : bool
Return true when all named parameters in $parms are 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) 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.
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-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.
setContentType()  : void
compileInFileMiddleware()  : array<int, MiddlewareInterface>
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.

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

$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|null

isAdmin()

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
bool

isAuthenticated()

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
bool

json()

JSON-encode $data with JSON_PRETTY_PRINT. Returns '{}' for non-array input.

public json(mixed $data) : string
Parameters
$data : mixed
Return values
string

paramsExists()

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
bool

processApi()

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
bool

resolveClubParam()

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|null

setContentType()

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
Return values
array<int, MiddlewareInterface>
On this page