Introduction

Middleware can accept parameters (throttle:60,1) and run code after the response (terminable middleware). This lesson covers parameter syntax, variadic parameters, response modification, and the terminate() hook.

Key Concepts

  • Parameter syntax: middleware('role:admin') — colon separates the alias from parameters.
  • Variadic parameters: handle($request, $next, string ...$roles) accepts any number.
  • Response modification: Code after $next can add headers, cookies, or rewrite the body.
  • Terminable middleware: terminate($request, $response) runs after the response is sent.
  • Singleton requirement: Terminable middleware must be bound as a singleton so the same instance sees handle and terminate.

Real World Context

Role checks, rate limits, and feature flags all take parameters. Without parameterised middleware you would need one class per role (EnsureAdmin, EnsureEditor) — that does not scale.

Deep Dive

Middleware can accept parameters and return different types of responses. This makes middleware highly flexible and reusable.

Middleware Parameters

Pass parameters to middleware after a colon:

php
Route::get('/admin', [AdminController::class, 'index'])
    ->middleware('role:admin');

Route::get('/posts', [PostController::class, 'index'])
    ->middleware('role:editor,admin');  // Multiple values

Receive parameters in the middleware:

php
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class EnsureUserHasRole
{
    /**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     * @param  string  ...$roles  // Variadic parameter for multiple roles
     */
    public function handle(Request $request, Closure $next, string ...$roles)
    {
        if (! $request->user() || ! $request->user()->hasAnyRole($roles)) {
            abort(403, 'Unauthorized action.');
        }

        return $next($request);
    }
}

Register with an alias:

php
$middleware->alias([
    'role' => EnsureUserHasRole::class,
]);

Middleware with Multiple Parameters

php
// Route definition
Route::get('/api/data', [ApiController::class, 'data'])
    ->middleware('throttle:60,1');  // 60 requests per 1 minute

// Middleware
public function handle(Request $request, Closure $next, int $maxAttempts, int $decayMinutes)
{
    // $maxAttempts = 60
    // $decayMinutes = 1
}

Common Middleware Responses

Redirect Response
php
public function handle(Request $request, Closure $next)
{
    if (! $request->user()?->hasVerifiedEmail()) {
        return redirect()->route('verification.notice');
    }

    return $next($request);
}
Abort Response
php
public function handle(Request $request, Closure $next)
{
    if ($request->user()?->isBanned()) {
        abort(403, 'Your account has been banned.');
    }

    return $next($request);
}
JSON Response (for APIs)
php
public function handle(Request $request, Closure $next)
{
    if (! $request->user()?->isSubscribed()) {
        return response()->json([
            'error' => 'Subscription required',
            'message' => 'Please upgrade your plan to access this feature.',
        ], 403);
    }

    return $next($request);
}

Modifying Requests

Add data to the request:

php
public function handle(Request $request, Closure $next)
{
    // Add data to request
    $request->merge(['injected_value' => 'something']);

    // Or set an attribute
    $request->attributes->set('start_time', microtime(true));

    return $next($request);
}

Modifying Responses

php
public function handle(Request $request, Closure $next)
{
    $response = $next($request);

    // Add headers
    $response->header('X-Frame-Options', 'DENY');
    $response->header('X-Content-Type-Options', 'nosniff');

    return $response;
}

Terminable Middleware

Run code after the response is sent to the browser:

php
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class LogSlowRequests
{
    public function handle(Request $request, Closure $next)
    {
        $request->attributes->set('start_time', microtime(true));

        return $next($request);
    }

    /**
     * Handle tasks after the response has been sent.
     */
    public function terminate(Request $request, $response): void
    {
        $duration = microtime(true) - $request->attributes->get('start_time');

        if ($duration > 1.0) {  // Longer than 1 second
            Log::warning('Slow request', [
                'url' => $request->fullUrl(),
                'duration' => round($duration, 2) . 's',
            ]);
        }
    }
}

Terminable middleware must be registered as a singleton:

php
// In AppServiceProvider
$this->app->singleton(LogSlowRequests::class);

Practical Example: API Version Middleware

php
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class ApiVersion
{
    public function handle(Request $request, Closure $next, string $version)
    {
        // Check Accept header for version
        $acceptHeader = $request->header('Accept', '');

        if (str_contains($acceptHeader, "application/vnd.api.v{$version}")) {
            return $next($request);
        }

        // Check URL for version
        if ($request->segment(2) === "v{$version}") {
            return $next($request);
        }

        return response()->json([
            'error' => 'API version mismatch',
            'expected' => $version,
        ], 400);
    }
}

// Usage
Route::prefix('api/v1')
    ->middleware('api.version:1')
    ->group(function () {
        // v1 routes
    });

Common Pitfalls

  1. Forgetting the variadic operator — string $roles only accepts one value. string ...$roles accepts any number.
  2. Reading terminal middleware state from a different instance — Always bind terminable middleware as a singleton.

Best Practices

  1. Use named rate limiters over inline throttle values — Centralises rate-limit policy.
  2. Log slow requests in terminate() — It runs after the response is sent so it never blocks the user.

Summary

  • Parameters attach to middleware with a colon: role:admin.
  • Variadic string ...$values accepts multiple parameters.
  • Code after $next modifies the response on the way out.
  • terminate($request, $response) runs after the response is delivered.
  • Terminable middleware must be registered as a singleton.
✓ Completed