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:
| Middleware | What it does |
|---|---|
ErrorHandlerMiddleware | Turns exceptions into error pages |
AssetMiddleware | Serves plugin files |
RoutingMiddleware | Finds the route for an address |
BodyParserMiddleware | Parses a JSON request body |
CsrfProtectionMiddleware | Checks 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.