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

ConditionalRequest
in package

FinalYes

RFC 9110 conditional-request evaluator — a pure, server-free port of Apache httpd's ap_meets_conditions() (modules/http/http_protocol.c).

The class is intentionally a bag of static, side-effect-free functions: it reads only the request method, the request headers (If-Match, If-Unmodified-Since, If-None-Match, If-Modified-Since), and the representation metadata that a server already computed (ETag, Last-Modified, plus the current request time). It returns the HTTP status the caller should emit — nothing more. This keeps it exhaustively unit testable without an OpenSwoole server.

Apache's precedence is mirrored EXACTLY:

  1. If-Match NOMATCH -> 412
  2. If-Unmodified-Since modified -> 412 (NOMATCH just blocks a later 304)
  3. If-None-Match match -> 304 (GET/HEAD) or 412 (other methods)
  4. If-Modified-Since not-modified -> 304 (GET/HEAD only)

(Apache's fifth step, If-Range, lives in the Range layer and is not part of this evaluator; the Range middleware owns that comparison.)

ETag comparison follows Apache's ap_find_etag_weak / ap_find_etag_strong (server/util.c): If-None-Match on GET/HEAD without a Range header uses WEAK comparison (W/"x" matches "x"); If-Match, and If-None-Match on any other method or when a Range header is present, use STRONG comparison (a weak tag on either side never matches).

Table of Contents

Constants

COND_NOMATCH  : mixed = 1
The condition was present and did not match.
COND_NONE  : mixed = 0
No conditional header of this kind was present.
COND_STRONG  : mixed = 3
The condition matched using strong comparison semantics.
COND_WEAK  : mixed = 2
The condition matched using weak comparison semantics.

Methods

evaluate()  : int
Evaluate the conditional-request preconditions for a representation.
findEtagStrong()  : bool
Strong ETag comparison — port of ap_find_etag_strong / find_list_item with AP_ETAG_STRONG. The stored ETag must be strong (start with "), and any weak entries in the request list are skipped. Returns true when the strong stored tag appears in the comma-separated request list.
findEtagWeak()  : bool
Weak ETag comparison — port of ap_find_etag_weak / find_list_item with AP_ETAG_WEAK. Both the stored tag and each request-list entry have any W/ prefix stripped before the opaque quoted-strings are compared, so W/"x" matches "x" and vice versa.
ifMatch()  : int
ap_condition_if_match — strong comparison only; '*' matches any tag.
ifModifiedSince()  : int
ap_condition_if_modified_since.
ifNoneMatch()  : int
ap_condition_if_none_match.
ifUnmodifiedSince()  : int
ap_condition_if_unmodified_since.
header()  : string|null
Case-insensitive request-header lookup. Returns the raw value, or null when the header is absent (an empty-string value is treated as present).
isGetOrHead()  : bool
Whether the method takes the GET/HEAD conditional path (304-eligible).
isWildcard()  : bool
True when a conditional header value is the * wildcard (allowing only surrounding whitespace), matching Apache's value[0] == '*' test against an already-trimmed header.
parseHttpDate()  : int|null
Parse an HTTP-date into a Unix timestamp, or null when unparseable.
splitList()  : array<int, string>
Split a comma-separated ETag list header into trimmed, non-empty tokens.
stripWeak()  : string|null
Strip a leading W/ weak marker from an ETag token. Returns the bare quoted-string, or null when the token is not a valid (quoted) ETag.

Constants

COND_NOMATCH

The condition was present and did not match.

public mixed COND_NOMATCH = 1

COND_NONE

No conditional header of this kind was present.

public mixed COND_NONE = 0

COND_STRONG

The condition matched using strong comparison semantics.

public mixed COND_STRONG = 3

COND_WEAK

The condition matched using weak comparison semantics.

public mixed COND_WEAK = 2

Methods

evaluate()

Evaluate the conditional-request preconditions for a representation.

public static evaluate(string $method, array<string, string> $reqHeaders, string $etag[, int|null $lastModified = null ][, int|null $requestTime = null ]) : int

Mirrors ap_meets_conditions(). Only invoked for otherwise-successful (2xx) responses — callers must skip error responses, exactly as Apache guards on ap_is_HTTP_SUCCESS(r->status).

Parameters
$method : string

The request method (e.g. "GET", "HEAD", "PUT").

$reqHeaders : array<string, string>

Lowercased-or-mixed request header map; only the four conditional headers and "range" are consulted. Lookups are case-insensitive.

$etag : string

The representation's current ETag (may be weak W/"..." or strong "..."), or '' when none is known.

$lastModified : int|null = null

Representation mtime as a Unix timestamp, or null when unknown.

$requestTime : int|null = null

The server's request-receipt time as a Unix timestamp; defaults to time(). Used as the upper bound that makes future-dated If-Modified-Since / If-Unmodified-Since values invalid.

Return values
int

The HTTP status the caller should emit: 200 (proceed), 304 (Not Modified), or 412 (Precondition Failed).

findEtagStrong()

Strong ETag comparison — port of ap_find_etag_strong / find_list_item with AP_ETAG_STRONG. The stored ETag must be strong (start with "), and any weak entries in the request list are skipped. Returns true when the strong stored tag appears in the comma-separated request list.

public static findEtagStrong(string $list, string $etag) : bool
Parameters
$list : string
$etag : string
Return values
bool

findEtagWeak()

Weak ETag comparison — port of ap_find_etag_weak / find_list_item with AP_ETAG_WEAK. Both the stored tag and each request-list entry have any W/ prefix stripped before the opaque quoted-strings are compared, so W/"x" matches "x" and vice versa.

public static findEtagWeak(string $list, string $etag) : bool
Parameters
$list : string
$etag : string
Return values
bool

ifMatch()

ap_condition_if_match — strong comparison only; '*' matches any tag.

public static ifMatch(array<string, string> $reqHeaders, string $etag) : int
Parameters
$reqHeaders : array<string, string>
$etag : string
Return values
int

ifModifiedSince()

ap_condition_if_modified_since.

public static ifModifiedSince(array<string, string> $reqHeaders, int|null $lastModified, int $requestTime, bool $hasRange) : int

Returns WEAK/STRONG (not-modified -> candidate 304) only when the IMS date is within [mtime, requestTime]; a future-dated IMS is invalid (NOMATCH). A Range header forbids the weak (within-60s) outcome.

Parameters
$reqHeaders : array<string, string>
$lastModified : int|null
$requestTime : int
$hasRange : bool
Return values
int

ifNoneMatch()

ap_condition_if_none_match.

public static ifNoneMatch(array<string, string> $reqHeaders, string $etag, bool $isGetOrHead, bool $hasRange) : int

'*' matches unconditionally (STRONG). GET/HEAD without a Range header use weak comparison; any other method, or a Range request, requires strong comparison.

Parameters
$reqHeaders : array<string, string>
$etag : string
$isGetOrHead : bool
$hasRange : bool
Return values
int

ifUnmodifiedSince()

ap_condition_if_unmodified_since.

public static ifUnmodifiedSince(array<string, string> $reqHeaders, int|null $lastModified, int $requestTime, bool $hasRange) : int

NOMATCH means the resource was NOT modified (precondition met). WEAK or STRONG means it WAS modified after the supplied date (precondition fails -> 412). A Range header forbids the weak (within-60s) outcome.

Parameters
$reqHeaders : array<string, string>
$lastModified : int|null
$requestTime : int
$hasRange : bool
Return values
int

header()

Case-insensitive request-header lookup. Returns the raw value, or null when the header is absent (an empty-string value is treated as present).

private static header(array<string, string> $reqHeaders, string $name) : string|null
Parameters
$reqHeaders : array<string, string>
$name : string
Return values
string|null

isGetOrHead()

Whether the method takes the GET/HEAD conditional path (304-eligible).

private static isGetOrHead(string $method) : bool
Parameters
$method : string
Return values
bool

isWildcard()

True when a conditional header value is the * wildcard (allowing only surrounding whitespace), matching Apache's value[0] == '*' test against an already-trimmed header.

private static isWildcard(string $value) : bool
Parameters
$value : string
Return values
bool

parseHttpDate()

Parse an HTTP-date into a Unix timestamp, or null when unparseable.

private static parseHttpDate(string $value) : int|null

Accepts the three RFC 9110 §5.6.7 formats (strtotime covers IMF-fixdate, RFC 850, and asctime). Empty / malformed values yield null, mirroring Apache's APR_DATE_BAD.

Parameters
$value : string
Return values
int|null

splitList()

Split a comma-separated ETag list header into trimmed, non-empty tokens.

private static splitList(string $list) : array<int, string>

Commas only ever separate ETags here — they cannot appear unescaped inside the opaque quoted-string of an entity-tag (RFC 9110 §8.8.3).

Parameters
$list : string
Return values
array<int, string>

stripWeak()

Strip a leading W/ weak marker from an ETag token. Returns the bare quoted-string, or null when the token is not a valid (quoted) ETag.

private static stripWeak(string $tag) : string|null
Parameters
$tag : string
Return values
string|null
On this page