Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Middleware

A middleware is a class that every request passes through on its way to a controller, and every response on its way back. Middleware stand in a queue: one parses the route, another checks the form token, a third catches errors. This way tasks common to all pages are handled before the controller.

Where the queue is

The queue is described in the middleware() method of src/Application.php. In a new project it is already filled:

MiddlewareWhat it does
ErrorHandlerMiddlewareTurns exceptions into error pages
AssetMiddlewareServes plugin files
RoutingMiddlewareFinds the route for an address
BodyParserMiddlewareParses a JSON request body
CsrfProtectionMiddlewareChecks the form token

Creating

Your own middleware live in the src/Middleware folder:

bin/cake bake middleware Timer
// src/Middleware/TimerMiddleware.php
namespace App\Middleware;
 
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
 
class TimerMiddleware implements MiddlewareInterface
{
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        return $handler->handle($request);
    }
}

This middleware does nothing. $handler->handle($request) passes the request further down the queue and returns the finished response.

Code before and after

public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
    $start = microtime(true);
 
    $response = $handler->handle($request);
 
    $ms = (int) round((microtime(true) - $start) * 1000);
 
    return $response->withHeader('X-Response-Time', $ms . 'ms');
}

Everything written before the handle() call runs before the controller, and everything after it runs when the response is ready. Here the middleware measures the time and adds it to a response header.

Registering

// src/Application.php
use App\Middleware\TimerMiddleware;
 
public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
{
    $middlewareQueue
        // ... the middleware that were already in the file
        ->add(new TimerMiddleware());
 
    return $middlewareQueue;
}

add() puts the middleware at the end of the queue. Open any page and look at the response headers on the Network tab of the browser tools: the X-Response-Time line is there.

A response without a controller

A middleware may stop the request and answer by itself:

use Cake\Http\Response;
 
if ($request->getUri()->getPath() === '/ping') {
    return (new Response())->withStringBody('pong');
}
 
return $handler->handle($request);

The /ping address returns the text pong, and routes and controllers are never reached. Access checks and maintenance mode work this way.

Only for some addresses

A middleware can be enabled for one group of routes:

// config/routes.php
$routes->scope('/api', function (RouteBuilder $builder): void {
    $builder->registerMiddleware('timer', new TimerMiddleware());
    $builder->applyMiddleware('timer');
 
    $builder->resources('Articles');
});

registerMiddleware() gives the middleware a name and applyMiddleware() applies it to all routes of this group.

Common mistake

The order in the queue matters: a middleware sees only what the middleware before it have done. AuthenticationMiddleware placed before RoutingMiddleware does not know the route and works incorrectly. When adding a plugin middleware, put it where its documentation says, usually after routing.

Thanks for reading!

Donate to the site — we work hard for you.