Middleware
Middleware provides a mechanism for filtering HTTP requests entering your application. They can inspect, modify, or reject requests before they reach your controllers.
Creating Middleware
Generate a new middleware using Craftsman:
php craftsman make:middleware AuthMiddleware
This creates a file in app/Middleware/AuthMiddleware.php:
<?php
namespace App\Middleware;
use ZephyrPHP\Core\Http\Request;
use ZephyrPHP\Core\Http\Response;
class AuthMiddleware
{
public function handle(Request $request, callable $next): Response
{
// Your middleware logic here
return $next($request);
}
}
Middleware Structure
Every middleware must have a handle method that:
- Receives the current
Request - Receives a
$nextcallable to pass control to the next middleware/controller - Returns a
Response
Before vs After Middleware
Before Middleware
Execute logic before the request is handled:
public function handle(Request $request, callable $next): Response
{
// This runs BEFORE the controller
if (!$this->isAuthorized($request)) {
return Response::redirect('/login');
}
return $next($request);
}
After Middleware
Execute logic after the response is created:
public function handle(Request $request, callable $next): Response
{
$response = $next($request);
// This runs AFTER the controller
$response->withHeader('X-Custom-Header', 'value');
return $response;
}
Common Middleware Examples
Authentication Middleware
<?php
namespace App\Middleware;
use ZephyrPHP\Core\Http\Request;
use ZephyrPHP\Core\Http\Response;
class AuthMiddleware
{
public function handle(Request $request, callable $next): Response
{
if (!session('user_id')) {
// Store intended URL for redirect after login
session()->set('intended_url', $request->getPath());
return Response::redirect('/login');
}
return $next($request);
}
}
Guest Middleware
<?php
namespace App\Middleware;
use ZephyrPHP\Core\Http\Request;
use ZephyrPHP\Core\Http\Response;
class GuestMiddleware
{
public function handle(Request $request, callable $next): Response
{
// Redirect authenticated users away from guest-only pages
if (session('user_id')) {
return Response::redirect('/dashboard');
}
return $next($request);
}
}
CORS Middleware
<?php
namespace App\Middleware;
use ZephyrPHP\Core\Http\Request;
use ZephyrPHP\Core\Http\Response;
class CorsMiddleware
{
private array $allowedOrigins = [
'http://localhost:3000',
'https://app.example.com',
];
public function handle(Request $request, callable $next): Response
{
// Handle preflight OPTIONS request
if ($request->getMethod() === 'OPTIONS') {
$response = new Response('', 204);
return $this->addCorsHeaders($response, $request);
}
$response = $next($request);
return $this->addCorsHeaders($response, $request);
}
private function addCorsHeaders(Response $response, Request $request): Response
{
$origin = $request->getHeader('Origin');
if ($origin && in_array($origin, $this->allowedOrigins)) {
$response->withHeader('Access-Control-Allow-Origin', $origin);
}
$response->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
$response->withHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
$response->withHeader('Access-Control-Allow-Credentials', 'true');
return $response;
}
}
Rate Limiting Middleware
<?php
namespace App\Middleware;
use ZephyrPHP\Core\Http\Request;
use ZephyrPHP\Core\Http\Response;
class RateLimitMiddleware
{
private int $maxRequests = 60;
private int $windowSeconds = 60;
public function handle(Request $request, callable $next): Response
{
$key = 'rate_limit:' . md5($request->getClientIp());
$data = session()->get($key, ['count' => 0, 'expires' => 0]);
if ($data['expires'] < time()) {
$data = ['count' => 0, 'expires' => time() + $this->windowSeconds];
}
if ($data['count'] >= $this->maxRequests) {
return Response::make('Too Many Requests', 429)
->withHeader('Retry-After', (string) $this->windowSeconds);
}
$data['count']++;
session()->set($key, $data);
$response = $next($request);
$response->withHeader('X-RateLimit-Remaining', (string) ($this->maxRequests - $data['count']));
return $response;
}
}
Using Middleware with Routes
Single Route
use App\Middleware\AuthMiddleware;
Route::get('/dashboard', [new DashboardController(), 'index'])
->middleware(new AuthMiddleware());
Route Groups
use App\Middleware\AuthMiddleware;
use App\Middleware\AdminMiddleware;
Route::group('/admin', function() {
Route::get('/dashboard', [new AdminController(), 'dashboard']);
Route::get('/users', [new AdminController(), 'users']);
Route::post('/users', [new AdminController(), 'createUser']);
})->middleware([
new AuthMiddleware(),
new AdminMiddleware(),
]);
Multiple Middleware
Route::get('/api/data', [new ApiController(), 'data'])
->middleware([
new CorsMiddleware(),
new AuthMiddleware(),
new RateLimitMiddleware(),
]);
Middleware with Parameters
Pass configuration to middleware via the constructor:
<?php
namespace App\Middleware;
class RoleMiddleware
{
private array $allowedRoles;
public function __construct(array $roles)
{
$this->allowedRoles = $roles;
}
public function handle(Request $request, callable $next): Response
{
$userRole = session('user_role');
if (!in_array($userRole, $this->allowedRoles)) {
abort(403, 'Access denied');
}
return $next($request);
}
}
// Usage
Route::get('/admin', [new AdminController(), 'index'])
->middleware(new RoleMiddleware(['admin', 'superadmin']));
Terminating Middleware
Execute code after the response has been sent:
<?php
namespace App\Middleware;
class LoggingMiddleware
{
public function handle(Request $request, callable $next): Response
{
$startTime = microtime(true);
$response = $next($request);
// Log after response
$duration = microtime(true) - $startTime;
logger()->info('Request completed', [
'path' => $request->getPath(),
'method' => $request->getMethod(),
'duration_ms' => round($duration * 1000, 2),
'status' => $response->getStatusCode(),
]);
return $response;
}
}
Best Practices
- Keep middleware focused - Each middleware should do one thing well
- Order matters - Middleware runs in the order they're registered
- Return early - Stop the chain when conditions aren't met
- Don't modify request data - Let controllers handle business logic
- Use dependency injection - Pass configuration via constructors
Built-in Middleware Concepts
ZephyrPHP handles some common middleware tasks automatically:
| Feature | How It's Handled |
|---|---|
| CSRF Protection | Use validateCSRF() in controllers |
| Security Headers | Applied automatically via Headers::apply() |
| Session Start | Started automatically in Application bootstrap |
Related
See Routing for applying middleware to routes, Security for built-in security features, and CSRF Protection.