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.

Middleware: The Wrap

Middleware is airport security between the gate and the plane. You don't write it for every flight. You nod at the TSA agent.

You will learn

  • What PSR-15 middleware actually is (in 30 seconds)
  • The six built-ins ZealPHP ships with — and when each one matters
  • How to write a custom middleware in about 10 lines
  • Why the registration order is reversed when the stack actually runs

What middleware is

A middleware is a function that gets the request before your handler, and gets the response after. It can short-circuit the request (returning 401 before authentication even reaches your route), add a header to every response (compression, CORS, ETag), measure timing, log, anything that should apply to many routes rather than one.

ZealPHP uses the PSR-15 middleware shape. A middleware implements:

public function process(
    ServerRequestInterface $request,
    RequestHandlerInterface $handler
): ResponseInterface;

You either return a response yourself (short-circuit) or call $handler->handle($request) to delegate to the next middleware in the chain. Same shape Slim, Symfony, Laravel (via adapters), and most modern PHP frameworks use.

The six built-ins

ZealPHP ships these out of the box. Most apps register the first four.

MiddlewareWhat it doesConfigure with
CorsMiddlewarePreflight (OPTIONS) handling and Access-Control-* headers on every response.Constructor args or ZEALPHP_CORS_ORIGINS
ETagMiddlewareWeak ETag on GET responses; returns 304 on If-None-Match match.None
CompressionMiddlewaregzip/deflate when client supports it. Skip if OpenSwoole’s built-in compression is on.None
RangeMiddlewareRFC 7233 Range requests: 206 Partial Content, multi-range, If-Range.None
SessionStartMiddlewareEager session start for first-time visitors. Without this, only returning visitors get sessions.ZEALPHP_SESSION_SECURE for HTTPS override
IniIsolationMiddlewareSnapshots php.ini changes per request so ini_set() doesn’t leak.ZEALPHP_INI_ISOLATE=1

Register them in app.php before $app->run():

$app->addMiddleware(new CorsMiddleware());
$app->addMiddleware(new ETagMiddleware());
$app->addMiddleware(new SessionStartMiddleware());

Order is reversed at execution time

You register A, B, C. The stack ZealPHP builds is C wraps B wraps A wraps ResponseMiddleware. At request time, C runs first. The last middleware you add is the outermost wrapper — same convention as Slim, Express, Laravel.

This means: register the inner-most concerns first, the outer-most last. Session handling is closest to the route handler — register early. CORS handles every response including 404s — register last.

Writing your own

Here’s a complete rate-limit-header middleware in 10 lines:

use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;

class RateLimitHeader implements MiddlewareInterface {
    public function __construct(private int $limit = 60) {}

    public function process($request, RequestHandlerInterface $handler) {
        $response = $handler->handle($request);
        return $response
            ->withHeader('X-RateLimit-Limit', (string)$this->limit)
            ->withHeader('X-RateLimit-Remaining', (string)($this->limit - 1));
    }
}

// in app.php
$app->addMiddleware(new RateLimitHeader(100));

The pattern: call $handler->handle(), modify the returned response, return it. For short-circuit behavior (e.g., auth that returns 401 before the route runs), build a response yourself with new \OpenSwoole\Core\Psr\Response(...) and return it without calling $handler->handle() at all.

When NOT to write middleware

If the logic applies to one route, it goes in the handler. If it’s really tangled with request-specific data, it goes in the handler. If you find yourself reaching for middleware to validate a single form, you’ve over-engineered — just validate in the handler and return 422.

Middleware shines for cross-cutting concerns: things every route benefits from (CORS, logging, sessions) or things you can turn on/off as a feature flag (compression, rate-limiting, auth-required gates).

Try it live

The demo app registers CORS + ETag + Session-start + Range. See the headers in action:

You register middleware in the order: A, B, C. A request arrives. Which middleware sees the inbound request first?

Key Takeaways

  • A middleware is a function that wraps every request: (request, $handler) => response.
  • ZealPHP follows PSR-15 — same shape as Slim, Symfony, modern Laravel.
  • Six built-ins: CORS, ETag, Compression, Range, SessionStart, IniIsolation.
  • Register order is reversed at execution: last-added runs first (outermost).
  • Middleware is for cross-cutting concerns — per-route logic still belongs in the handler.