Swoft2 框架精华教程:Middlewares 优先级
- 作者: 刘杰
- 来源: 技术那些事
- 阅读:219
- 发布: 2025-06-19 12:04
- 最后更新: 2025-06-19 12:04
详细说明
swoft2 中的中间件相当于 java 的请求拦截器,用来拦截服务器的每个请求,所以拦截器的顺序很重要,因为有些校验必须按照一定的顺序才有意义。比如,要先验证用户是否登录,然后在验证接口是否有访问权限。如果反过来,未验证登录的时候,就不存在验证接口权限一说。所以,通过以下分析,希望能够加强对swoft 中间件(拦截器)的理解和使用。
中间件的主要顺序
配置在 bean.php 文件中的全局的中间件,有三种类型:
- preMiddlewares
- middlewares
- afterMiddlewares
这三个中间件执行的的顺序为 preMiddlewares => middlewares => afterMiddlewares。
通过 Middleware和 Middlewares配置的自定义的中间件,是通过 afterMiddleware()方法。此方法中默认有一个 UserMiddlewares::class,此中间件用来收集注解标注的中间件类,将其动态的插入到已经合并后的 middlewares 属性中,插入的位置就是下一个要执行的中间件(即 UserMiddleware::class后)。
举例说明:
假如用户请求 /user/login 接口,UserController 上有个Middleware 注解:X::class,那么此中间件执行位置如下:
php
// bean.php 中配置的中间件如下:
return [
'httpDispatcher' => [
'preMiddlewares' => [
A::class
],
// Add global http middleware
'middlewares' => [
B::class,
],
'afterMiddlewares' => [
// UserMiddleware::class 此处相当于有此中间件
// 实际如果访问路径有中间价注解,会动态放到此位置执行 <=
C::class
]
],
];
// requestMiddlewares: [A::class, B::class, UserMiddleware::class, C::class]
// 每次请求时候,会将 requestMiddlewares 重新覆盖 middlewares,达到动态中间件的结果.
先执行 A::class 对应的实例,然后执行 B::class 的实例,然后是 UserMiddleware::class 的实例。
执行 UserMiddleware::class 实例,本质上,是将动态收集到当前执行方法上的中间件注解类(X::class),插到UserMiddleware::class 的位置之后。这样此时会执行 X::class 的实例,最后,才执行 C::class 的实例。由于每次请求进来后,会通过 initialize()方法,将 requestMiddlewares 重新放置于 middlewares 之中,这就会造成每个请求会动态的加载注解标注的中间件的效果。
注意:UserMiddleware::class 中间件,会根据当前访问的路径,判断是否有动态的中间件注解,没有注解就不会动态加,这样可以提高执行的效率。
源码依据
src/HttpDispatcher.php
php
/**
* Init 初始化中间件代码
* 注意:array_merge 合并两个数字索引数组,会按照两个数组的顺序进行合并(参数靠前的最终靠前)
*/
public function init(): void
{
// 前置中间件,优先合并通过 pre 方法的中间件,然后是通过 preMiddlewares 属性配置的中间件
$this->preMiddlewares = array_merge($this->preMiddleware(), $this->preMiddlewares);
// 后置中间件,同样合并时候通过方法获取到的优先级要高于通过属性配置进去的(Bean定义方式)
$this->afterMiddlewares = array_merge($this->afterMiddleware(), $this->afterMiddlewares);
// 最后请求通过的所有中间件,先是 pre 然后是 middlewares,然后是 after
$this->requestMiddlewares = array_merge($this->preMiddlewares, $this->middlewares, $this->afterMiddlewares);
}
/**
* Dispatch http request
*
* @param mixed ...$params
*/
public function dispatch(...$params): void
{
/**
* @var Request $request
* @var Response $response
*/
[$request, $response] = $params;
/* @var RequestHandler $requestHandler */
$requestHandler = Swoft::getBean(RequestHandler::class);
try {
// 注意这一句,会将中间件重置
$requestHandler->initialize($this->requestMiddlewares, $this->defaultMiddleware);
// Before request
$this->beforeRequest($request, $response);
// Trigger before handle event
Swoft::trigger(HttpServerEvent::BEFORE_REQUEST, null, $request, $response);
// Match router and handle
$request = $this->matchRouter($request);
$response = $requestHandler->handle($request);
} catch (Throwable $e) {
/** @var HttpErrorDispatcher $errDispatcher */
$errDispatcher = Swoft::getSingleton(HttpErrorDispatcher::class);
// Handle request error
$response = $errDispatcher->run($e, $response);
}
try {
// Format response content type
$response = $this->acceptFormatter->format($response);
// Trigger after request
Swoft::trigger(HttpServerEvent::AFTER_REQUEST, null, $response);
// After request
$this->afterRequest($response);
} catch (Throwable $e) {
Error::log('response error=%s(%d) at %s:%d', $e->getMessage(), $e->getCode(), $e->getFile(), $e->getLine());
}
}
/**
* @return array
*/
public function afterMiddleware(): array
{
return [
UserMiddleware::class
];
}
src/Middleware/UserMiddleware.php
此中间件的执行过程,会校验当前访问的路径上是否有通过注解标注的中间件(拦截器),如果有,则会自动加入到 UserMiddleware::class之后。
php
class UserMiddleware implements MiddlewareInterface
{
/**
* @param ServerRequestInterface $request
* @param RequestHandlerInterface $handler
*
* @return ResponseInterface
* @throws HttpServerException
*/
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
// Route data
$routeData = $request->getAttribute(Request::ROUTER_ATTRIBUTE);
/* @var Route $route */
[$status, , $route] = $routeData;
if ($status !== Router::FOUND) {
return $handler->handle($request);
}
// Controller and method
$handlerId = $route->getHandler();
[$className, $method] = explode('@', $handlerId);
$middlewares = MiddlewareRegister::getMiddlewares($className, $method);
if (!empty($middlewares) && $handler instanceof RequestHandler) {
$handler->insertMiddlewares($middlewares);
}
return $handler->handle($request);
}
}