Middleware provides a convenient way to filter HTTP requests entering your application. Think of it as a chain of responsibility where each middleware can inspect, modify, or reject a request.
Request → [Middleware A] → [Middleware B] → [Controller] → Response
↓ ↓
Can reject Can modify
Create app/Http/Middleware/LogRequestsMiddleware.php:
<?php
declare(strict_types=1);
namespace App\Http\Middleware;
use Marwa\Framework\Middlewares\AbstractMiddleware;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
class LogRequestsMiddleware extends AbstractMiddleware
{
public function handle(ServerRequestInterface $request): ServerRequestInterface|ResponseInterface
{
// Log the request
logger()->info('Request received', [
'method' => $request->getMethod(),
'uri' => (string) $request->getUri(),
]);
// Continue to next middleware
return $this->next($request);
}
}
Create app/Http/Middleware/AddHeaderMiddleware.php:
<?php
declare(strict_types=1);
namespace App\Http\Middleware;
use Marwa\Framework\Middlewares\AbstractMiddleware;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
class AddHeaderMiddleware extends AbstractMiddleware
{
public function handle(ServerRequestInterface $request): ServerRequestInterface|ResponseInterface
{
// Process request first
$request = $this->next($request);
// If we got a response back, add header
if ($request instanceof ResponseInterface) {
return $request->withHeader('X-Custom-Header', 'value');
}
return $request;
}
}
Add to config/app.php:
return [
'middlewares' => [
Marwa\Framework\Middlewares\RequestIdMiddleware::class,
App\Http\Middleware\LogRequestsMiddleware::class,
App\Http\Middleware\AddHeaderMiddleware::class,
],
];
<?php
use Marwa\Framework\Facades\Router;
use App\Http\Middleware\AuthMiddleware;
Router::get('/dashboard', fn() => Response::html('<h1>Dashboard</h1>'))
->middleware(AuthMiddleware::class)
->register();
Router::post('/admin/users', function() {
return Response::html('<h1>Create User</h1>');
})
->middleware(['auth', 'admin'])
->register();
Marwa Framework includes several built-in middleware:
| Middleware | Description |
|---|---|
RequestIdMiddleware |
Adds unique request ID |
SessionMiddleware |
Starts/manages sessions |
SecurityMiddleware |
Security headers & CSRF |
RouterMiddleware |
Routes the request |
DebugbarMiddleware |
Enables debug bar |
MaintenanceMiddleware |
Shows maintenance page |
<?php
use Marwa\Framework\Middlewares\AbstractMiddleware;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
class RoleMiddleware extends AbstractMiddleware
{
public function __construct(
private string $role
) {}
public function handle(ServerRequestInterface $request): ServerRequestInterface|ResponseInterface
{
$user = $request->getAttribute('user');
if ($user?->role !== $this->role) {
return Response::json(['error' => 'Forbidden'], 403);
}
return $this->next($request);
}
}
Register with parameter:
Router::get('/admin', fn() => Response::html('Admin'))
->middleware(['role:admin'])
->register();
<?php
use Marwa\Framework\Middlewares\AbstractMiddleware;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
use Marwa\Router\Response;
class AuthMiddleware extends AbstractMiddleware
{
public function handle(ServerRequestInterface $request): ServerRequestInterface|ResponseInterface
{
$session = session();
if (!$session->has('user_id')) {
return Response::redirect('/login');
}
// Add user to request attributes
$request = $request->withAttribute('user_id', $session->get('user_id'));
return $this->next($request);
}
}
<?php
use Marwa\Framework\Middlewares\AbstractMiddleware;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
class CorsMiddleware extends AbstractMiddleware
{
public function handle(ServerRequestInterface $request): ServerRequestInterface|ResponseInterface
{
$response = $this->next($request);
if ($response instanceof ResponseInterface) {
return $response
->withHeader('Access-Control-Allow-Origin', '*')
->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
->withHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
}
return $response;
}
}
<?php
use Marwa\Framework\Middlewares\AbstractMiddleware;
use Marwa\Router\Response;
class ThrottleMiddleware extends AbstractMiddleware
{
public function handle(ServerRequestInterface $request): ServerRequestInterface|ResponseInterface
{
$ip = $request->getServerParams()['REMOTE_ADDR'] ?? 'unknown';
$key = 'rate_limit:' . $ip;
if (throttle($key, 60, 60)) {
return Response::json([
'error' => 'Too Many Requests'
], 429);
}
return $this->next($request);
}
}
Group middleware for reuse:
// Define groups in config/app.php
return [
'middlewareGroups' => [
'web' => [
App\Http\Middleware\SessionMiddleware::class,
App\Http\Middleware\CsrfMiddleware::class,
],
'api' => [
App\Http\Middleware\ApiAuthMiddleware::class,
App\Http\Middleware\ThrottleMiddleware::class,
],
],
];
Middleware executes in the order they’re registered:
// Middleware A runs first, then B, then C
$app->addMiddleware(A::class);
$app->addMiddleware(B::class);
$app->addMiddleware(C::class);
// Request flow: A → B → C → Controller
// Response flow: Controller → C → B → A