Laravel中间件必须调用$next($request)才能延续Pipeline链式执行,否则请求静默中断;其执行顺序由$middleware、$middlewareGroups和路由middleware()注册位置共同决定,数组靠前者先处理请求、后处理响应。

中间件不是“可有可无的装饰”,而是 Laravel 请求生命周期中不可跳过的执行节点——不理解它的链式本质,写出来的中间件大概率会静默失效或阻断整个请求。
中间件 handle() 里为什么必须调用 $next($request)
因为 Laravel 的中间件是靠 Pipeline(管道)串起来的:每个中间件都只负责“自己那段”,然后显式把控制权交给下一个。如果在 handle() 方法里漏掉 $next($request),请求就卡死在这里,控制器永远不会收到它,也不会抛错,只会返回空响应或 500。
- 常见错误现象:
Route::get('/admin', [AdminController::class, 'index'])->middleware('check.role');访问时页面空白,日志无报错,浏览器 Network 面板显示响应状态为 200 但 body 为空 - 调试技巧:在
handle()开头加Log::info('check.role entered');,末尾加Log::info('check.role passed');,若只看到第一条日志,基本就是没调$next - 注意:即使你打算拦截请求(比如重定向或返回 403),也**不能**用
exit或die终止,而应直接return response(...)或return redirect(...)—— 这样框架仍能正常收尾,不会破坏响应头或 session
如何创建并注册一个带参数的中间件(如 role:admin,editor)
路由中间件支持传参,但参数格式和解析方式得自己约定清楚,Laravel 不自动拆解冒号后的内容。
- 创建命令:
php artisan make:middleware CheckRole - 关键修改点:在
handle()方法签名里追加参数变量(如$roles),它会自动接收冒号后的字符串(role:admin,editor→$roles = 'admin,editor') - 手动解析示例:
$roles = explode(',', $roles);,再结合Auth::user()->hasRole($role)判断 - 注册位置:必须加到
app/Http/Kernel.php的$routeMiddleware数组里,例如:'role' => \App\Http\Middleware\CheckRole::class,;不能放$middleware(全局数组),否则参数无法传递 - 使用方式:
->middleware('role:admin,editor');多个中间件可链式写:->middleware(['auth', 'role:admin'])
全局中间件 vs 路由中间件:什么时候该用哪个
选错注册位置,轻则功能无效,重则拖慢所有请求(比如把日志中间件误配成全局)。
-
$middleware(全局):适用于所有请求都必须走的逻辑,比如TrustProxies、PreventRequestsDuringMaintenance。自定义中间件极少适合放这里——比如你在里面dump()一个时间戳,那连 favicon.ico、AJAX 探针、健康检查接口全都会被打印,生产环境会疯 -
$routeMiddleware(路由):95% 的自定义中间件都应该注册在这里。它只在你显式调用middleware('xxx')时才加载,按需实例化,性能可控 - 容易踩的坑:把 CORS 中间件(
HandleCors)注册到$routeMiddleware却只在个别路由上启用,结果 API 接口跨域失败,而网页端正常——因为前端发的 OPTIONS 预检请求没经过该中间件
中间件执行顺序为什么会影响结果(比如日志中间件放错位置)
中间件不是并行执行的,而是严格按注册顺序(全局→分组→单个路由)和调用顺序(前置→控制器→后置)嵌套执行。顺序错了,看到的数据就不对。
- 典型问题:想记录响应状态码,但把日志中间件放在了
StartSession后面,结果发现$response->getStatusCode()总是 200 —— 因为 Session 中间件可能触发了重定向(302),但日志中间件在重定向发生前就执行了 - 正确做法:日志类中间件要尽量靠后(比如放在
$middleware数组末尾),确保它看到的是最终响应;权限类中间件要靠前,避免控制器已执行部分逻辑才被拦下 - 验证顺序:在每个中间件的
handle()前后各加一行Log::debug("middleware_name: before/after"),看日志时间戳就能理清链条











