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

ETagMiddleware
in package
implements MiddlewareInterface

ETag / Conditional-Request Middleware

Generates a weak ETag from a hash of the response body and evaluates the RFC 9110 conditional-request preconditions in Apache's ap_meets_conditions order via ConditionalRequest: If-Match -> If-Unmodified-Since -> If-None-Match -> If-Modified-Since.

Outcomes:

  • 304 Not ModifiedGET/HEAD whose validators say "unchanged".
  • 412 Precondition Failed — failed If-Match / If-Unmodified-Since, or a matched If-None-Match on a non-GET/HEAD method.
  • otherwise the original response, with an ETag header on GET/HEAD.

Usage in app.php: $app->addMiddleware(new \ZealPHP\Middleware\ETagMiddleware());

Streaming responses (SSE, stream(), Generator yield) are skipped — they have no buffered body to hash.

ETag derivation across paths (audit gap H7): ZealPHP emits weak ETags everywhere, but the validator's input depends on how the response is produced — exactly as Apache differs between static and dynamic content:

  • Buffered / dynamic responses (this middleware): W/"xxh3(body)".
  • Zero-copy file sends (Response::sendFile()): W/"mtime-size" (stat-based, no body hash — Apache never hashes a static file body either). The two paths are mutually exclusive per response: this middleware bails on streaming responses and on an empty buffered body (sendFile() streams), so it never overwrites a stat-based ETag. Because both forms are weak, a URL that switches serving path produces a cache miss (full 200), never a corrupt 304 — weak comparison can't false-match across the two namespaces.

Table of Contents

Interfaces

MiddlewareInterface

Methods

process()  : ResponseInterface
conditionalHeaders()  : array<string, string>
Collect the conditional-request headers the evaluator consults into a plain map. PSR-7 stores comma-joined values, which is exactly the list form {@see ConditionalRequest} parses.

Methods

process()

public process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
Parameters
$request : ServerRequestInterface
$handler : RequestHandlerInterface
Return values
ResponseInterface

conditionalHeaders()

Collect the conditional-request headers the evaluator consults into a plain map. PSR-7 stores comma-joined values, which is exactly the list form {@see ConditionalRequest} parses.

private conditionalHeaders(ServerRequestInterface $request) : array<string, string>
Parameters
$request : ServerRequestInterface
Return values
array<string, string>
On this page