中间件本质是接收$request和$next的闭包函数,需显式return $next($request)以避免请求卡死;注册方式分全局、分组及路由内联三种;修改请求须用$request->merge()等不可变方法。

中间件本质就是个闭包函数
PHP 本身没有“中间件”语法糖,Laravel 的中间件机制完全基于 Illuminate\Pipeline\Pipeline 和可调用对象(closure 或类方法)构建。你写的中间件,最终会被 pipeline 串成一条执行链,每个环节接收 $request、$next,必须显式调用 $next($request) 才能往下走。
常见错误是忘了 return $next($request),导致后续中间件和控制器完全不执行,请求静默卡死——页面白屏、无报错、network 面板里状态码停在 200 但响应体为空。
- 漏掉
return是最常踩的坑,尤其在加了条件判断后 - 不要在中间件里直接
echo或die,该用abort()或抛HttpException - 类中间件里
handle()方法必须声明第三个参数$next,哪怕不用也要写上
Laravel 中注册自定义中间件的三种方式
不是所有中间件都走 app/Http/Kernel.php。注册位置决定作用域和执行时机:
-
protected $middleware:全局中间件,每个 HTTP 请求都过一遍(含 Artisan 命令触发的请求) -
protected $middlewareGroups:按组启用,比如web组默认带 session、csrf;api组默认无 session - 路由内联注册:
Route::get('/foo', ...)->middleware('auth:sanctum'),只对当前路由生效
注意:自定义中间件类名要符合 PSR-4,且需在 Kernel.php 的 $middleware 或对应 group 里手动添加完整类名(如 \App\Http\Middleware\CheckAge),否则 Laravel 找不到它。
立即学习“PHP免费学习笔记(深入)”;
中间件里怎么安全读取和修改请求数据
中间件可以读写 $request 实例,但改法有讲究。Laravel 的 Request 是不可变对象(immutable),直接改 $request->query 或 $request->attributes 不生效。
- 想加参数供后续控制器用:用
$request->attributes->set('key', $value),控制器里通过$request->attributes->get('key')拿 - 想改查询参数或表单值:用
$request->merge(['foo' => 'bar'])返回新实例,再传给$next() - 别用
$_GET/$_POST,它们和 Laravel 的$request不同步,且在测试时无法 mock
示例:在中间件里注入用户偏好语言
public function handle($request, Closure $next)
{
$lang = $request->header('X-Preferred-Lang', 'zh');
$request = $request->merge(['preferred_lang' => $lang]);
return $next($request);
}
中间件性能与调试技巧
中间件是请求链上的“关卡”,每多一层就多一次函数调用和条件判断。高频接口里,一个没做缓存的 DB 查询中间件可能拖慢整个链路。
- 用
php artisan route:list --middleware快速确认某路由绑定了哪些中间件 - 在中间件开头加
if (app()->environment('local')) { Log::debug('check-age start'); },避免生产环境日志爆炸 - 涉及数据库或外部 API 调用的中间件,务必设超时、加缓存键(如基于
$request->ip()+$request->user()?->id)
真正难的不是写中间件,而是判断该不该把它塞进中间件——比如权限校验放中间件没问题,但根据用户行为动态生成菜单?那更适合放在控制器或服务类里做,中间件太重,也违背单一职责。











