Middleware

The seam everything else composes onto

A middleware is one method. It receives the request, and either calls $next to continue or answers on its own.

public function __invoke(Request $request, \Closure $next): Response;

Call $next($request) and you get back the Response from the rest of the pipeline — to return, replace, or add a header to. Return your own Response without calling it and nothing further runs. Throw an HttpException and the Kernel turns it into the error page, exactly as it would from an Action.

The list is written, not discovered

Middleware is declared in routes/middleware.php, beside routes/web.php. One file says where a request goes; the other says what it passes through.

return function (Env $env, Log $log): array {
    return [
        new OverridesMethod(),
        new VerifyCsrfToken(new Session(), $log),
    ];
};

The order things run in is the order they are written in, and nowhere else. The first in the list is the outermost — first to see a request, last to see a response. Middleware wraps routing as well as the Action, so a request that goes on to 404 still passes through it.

The framework composes nothing for you

TetherPHP starts no session, checks no CSRF token and honours no _method field of its own accord. An application that serves forms wants all three:

VerifyCsrfToken — issues a token and refuses any write that does not present it, in a csrf_token field or an X-CSRF-Token header. A rejected write is a 403, and the reason goes to the log rather than to the visitor.

OverridesMethod — reads _method from the body of a POST and rewrites the verb before routing, which is the only way a browser form can reach PUT, PATCH or DELETE. Only a POST is upgraded, so a link carrying ?_method=DELETE deletes nothing.

Leave them out and you have no session, no cookie and no magic field name — which is the right shape for a token-authenticated API, and for a site like this one where every route is a GET.

Building one must have no side effects

This is the contract the whole approach rests on. tether routes, tether explain and tether context build your middleware list in order to report what runs around a request — so a constructor that opens a connection or starts a session does it from a terminal too.

// wrong: work in the constructor
public function __construct() { $this->db = connect(); }

// right: work in __invoke()
public function __invoke(Request $request, \Closure $next): Response

Writing one

final class AddsSecurityHeaders implements MiddlewareInterface
{
    public function __invoke(Request $request, \Closure $next): Response
    {
        return $next($request)->withHeader('X-Frame-Options', 'DENY');
    }
}

Because the Kernel turns an HttpException into a Response inside the middleware, a header added on the way out lands on error pages too — which is the one class of response you least want to miss.