php中间件是遵循psr-15标准的请求/响应拦截机制,必须实现process()方法并显式调用$handler->handle($request)传递流程,否则请求静默终止;其执行遵循“洋葱模型”,注册顺序决定请求阶段执行顺序、响应阶段逆序执行,且options预检常被跳过。

PHP框架里的中间件不是“插件”或“钩子”,而是一套明确的请求/响应拦截与传递机制,核心在于它必须遵循 PSR-15 标准的 process() 方法签名,且必须显式调用 $handler->handle($request) 才能让流程继续——漏掉这一步,请求就静默终止了。
PSR-15 接口是硬性门槛,不是可选约定
所有合规中间件都得实现 MiddlewareInterface,它的 process() 方法接收两个参数:$request(Psr\Http\Message\ServerRequestInterface 实例)和 $handler(Psr\Http\Server\RequestHandlerInterface 实例)。这个 $handler 不是闭包,也不是任意 callable,而是下一个中间件或最终处理器的封装对象。
常见错误现象:
- 把
$next当成 Laravel 风格的Closure直接调用,结果报错Call to undefined method handle() - 在中间件里 return 一个数组或字符串,而不是
ResponseInterface实例,导致框架无法发送 HTTP 响应 - 忘记
use正确的 PSR 接口命名空间,造成类型提示失败或 IDE 误报
实操建议:
- 用
composer require psr/http-server-middleware psr/http-server-handler显式引入标准接口 - 在类顶部严格声明
implements MiddlewareInterface,让 IDE 和静态分析工具能校验方法签名 - 不要手动 new $handler;它由框架容器注入,你只负责调用其
handle()
Laravel 的 handle() 是封装,不是标准
Laravel 中间件用 handle($request, Closure $next) 是对 PSR-15 的上层适配,$next 实际是框架内部包装过的 RequestHandlerInterface 实现。这意味着:
- 你在 Laravel 里写的中间件不能直接复用于 Slim 或 Mezzio,除非重写为 PSR-15 兼容形式
-
$next($request)返回的是Illuminate\Http\Response,而 PSR-15 要求返回ResponseInterface,两者不兼容 - Laravel 的
terminate()方法是扩展行为,PSR-15 中没有对应定义,跨框架迁移时这部分逻辑需剥离或重写
性能影响:Laravel 的封装层带来轻微开销,但对绝大多数应用可忽略;真正影响性能的是中间件内部逻辑(如 DB 查询、远程 API 调用),而非调用方式本身。
“洋葱模型”决定执行顺序和资源释放时机
中间件链像洋葱一样层层包裹:请求从外向内穿透,响应从内向外折返。前置逻辑(如记录开始时间、解析 token)写在 $handler->handle($request) 之前,后置逻辑(如日志补全、Header 注入)写在之后。
典型陷阱:
- 在
$handler->handle($request)之后修改$request—— 此时请求已处理完毕,改了也无效 - 在前置部分抛出异常但没 catch,导致后续中间件完全不执行,也无法进入后置清理逻辑
- 把需要“响应后执行”的逻辑(如统计耗时)写在
$handler调用前,结果测出来全是 0ms
实操建议:
- 用
try...finally包裹$handler->handle($request),确保无论是否异常,后置清理都能运行 - 避免在中间件中做阻塞 IO(如 file_get_contents、curl_exec),否则会卡住整个洋葱层
- 如果中间件需读取请求体(
$request->getBody()),注意 PSR-7 的 body 是只读流,多次读取会失败,需提前缓存
注册顺序 = 执行顺序,但 OPTIONS 预检常被忽略
中间件按注册顺序从前到后执行请求阶段,逆序执行响应阶段。但有个关键例外:CORS 中的 OPTIONS 预检请求,在多数框架中会绕过大部分中间件,直奔路由匹配。这意味着:
- 你写的鉴权中间件不会处理
OPTIONS请求,所以前端发预检时 401/403 报错,实际是 CORS 头缺失导致 - 日志中间件可能完全收不到
OPTIONS记录,误判“没收到请求” - 如果你在中间件里依赖
$_SESSION或Auth::user(),它们在OPTIONS中通常未初始化
解决办法很直接:
- 在 CORS 中间件中显式检查
$request->getMethod() === 'OPTIONS',并立即返回带Access-Control-Allow-*头的空响应 - 把 CORS 中间件放在链最前面,确保它最先响应预检
- 不要在
OPTIONS流程中调用任何业务逻辑中间件(如 auth、throttle)
真正的复杂点不在写法,而在调试:当某个中间件“没生效”,第一反应不该是查代码,而是抓包看请求方法、确认是否被预检跳过、再检查注册位置是否在路由中间件组之外。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











