[https://learnku.com/articles/30768](https://learnku.com/articles/30768)
中间件
从请求过程中可以看出,第一步就是加载的中间件。那么如何加载的呢?看下面这段代码
$this->app->middleware
让 app 实例访问属性 middleware?你会发现实例中并没有这个属性,那么访问一个不存在的属性会发生什么呢?它会去访问 __get 魔术方法,你有这个想法之后会在 Container 中发现这个魔术方法,最终它会去 make 创建对象,对于 make 的过程请到 解析 Request 章节查看。
创建 middleware 的过程中有一个细节,就是加载配置文件 middleware.php,进入到 think\Middleware 文件之后,你会发现这么一段代码。
public static function __make(App $app, Config $config)
{
return (new static($config->get('middleware')))->setApp($app);
}
正如之前所提到的那样,使用 make 方法创建对象会执行默认方法 __make,所以这个时候就会将 config 中的 middleware.php 加载进来。目前这个版本似乎没有加入这个配置文件,你可以手动添加。
来看下面一段代码,从这段代码引出中间件的加载过程。
$this->app->middleware->import(include $this->app->getBasePath() . 'middleware.php');
在整个请求过程,首先加载的是 app/middleware.php 文件中的中间件,默认提供了四个中间件。但是这些都是不加载,你可自行选择打开。然后来看看 import 如何加载的。
public function import(array $middlewares = [], string $type = 'route'): void
{
foreach ($middlewares as $middleware) {
$this->add($middleware, $type);
}
}
$type 参数标记了路由的类型,目前只看到 route 和 controller 两种类型。默认全局中间件是 route 类型的。add 方法才是实实在在的导入。来看看的这个方法做了哪些事儿。
public function add($middleware, string $type = 'route'): void
{
// null 直接返回
if (is_null($middleware)) {
return;
}
// 创建中间件
$middleware = $this->buildMiddleware($middleware, $type);
// 加入到中间件队列,队列也是分类型的
if ($middleware) {
$this->queue[$type][] = $middleware;
}
}
buildMiddleware 方法用来创建中间件,做一些解析的工作。来看看代码的过程,直接在注释中解释
protected function buildMiddleware($middleware, string $type = 'route'): array
{
// 数组类型的 第一个参数必须是中间件,第二个是传入的参数
if (is_array($middleware)) {
list($middleware, $param) = $middleware;
}
// 如果是 middleware 是闭包, 直接返回
if ($middleware instanceof \Closure) {
return [$middleware, $param ?? null];
}
// 不支持非字符串的类型
if (!is_string($middleware)) {
throw new InvalidArgumentException('The middleware is invalid');
}
// 支持键值对,正如官方手册中提到,例如这样 [ 'hello' => middleware ]
if (isset($this->config[$middleware])) {
$middleware = $this->config[$middleware];
}
// 当你从配置文件解析出来是数组格式,就递归调用
if (is_array($middleware)) {
$this->import($middleware, $type);
return [];
}
// 最后返回一个 middware 的类名和默认方法 'handle'
// handle 方法对中间件而言是必须的,不然无法执行
return [[$middleware, 'handle'], $param ?? null];
}
然后返回会加入到 Queue 队列中,这个时候你打开全局中间件的其中一个话,队列中将会是这样的内容。
array(1) {
["route"]=>
array(1) {
[0]=>
array(2) {
[0]=>
array(2) {
[0]=>
string(34) "think\middleware\CheckRequestCache"
[1]=>
string(6) "handle"
}
[1]=>
NULL
}
}
}
这个和我们预期是一样的,在代码解释过程也没有遇到任何阻碍。我们继续往下看,这里仅仅是加入的过程,在后面请求执行的时候来看看如何执行的这些中间件的。
中间件执行
中间件的是由 Diapatch 方法执行,而核心在 resolve 方法,来看一下这个方法做了什么。
protected function resolve(string $type = 'route')
{
return function (Request $request) use ($type) {
$middleware = array_shift($this->queue[$type]);
if (null === $middleware) {
throw new InvalidArgumentException('The queue was exhausted, with no response returned');
}
// 1
list($call, $param) = $middleware;
// 2
if (is_array($call) && is_string($call[0])) {
$call = [$this->app->make($call[0]), $call[1]];
}
try {
// 3
$response = $this->app->invoke($call, [$request, $this->resolve($type), $param]);
} catch (HttpResponseException $exception) {
$response = $exception->getResponse();
}
if (!$response instanceof Response) {
throw new LogicException('The middleware must return Response instance');
}
return $response;
};
}
按照 1,2,3 的步骤进行说明。
队列使用了 array_shift 来进行,说明会执行先进来的中间件。所以你可以在到达控制器之前做很多业务应用初始化的事情。
从上面可以知道中间件队列的数据结构,所以步骤以就是解析出来 middleware 中间件类和参数。
如果是数组结构直接解析出对象
最重要的就是第三步了,利用反射来执行中间件类,注意在这个执行过程中递归调用了,而且记住 resolve 返回的始终是闭包,然后在中间件之间传递,你可以观察中间件的 handle 方法的参数,Request 对象,第二个闭包,第三个可选参数,正好对上了这个数组参数接口,所以 handle 方法的 $next() 就是在消费队列。
所以简单来说,中间件的过程就是利用队列在执行消费。保证了中间件的顺序执行。
文章转载于 thinkphp6 源码分析之中间件分析
- 空白目录
- php语法结构
- 安装与更新
- 开启调试模式及代码跟踪器
- 架构
- 源码分析
- 应用初始化
- 请求流程
- 中间件源码分析
- 请求处理源码分析
- Request源码分析
- 模板编译流程
- 路由与请求流程
- 容器
- 获取目录位置
- 入口文件
- 多应用模式及URL访问
- 依赖注入与容器
- 容器属性及方法
- Container
- App
- facade
- 中间件(middleware)
- 系统服务
- extend 扩展类库
- 笔记
- 配置
- env配置定义及获取
- 配置文件的配置获取
- 单应用模式-(配置)文件目录结构(默认)
- 多应用模式(配置)文件目录结构(配置文件)
- 配置文件
- 应用配置:app.php
- 缓存配置: cache.php
- 数据库配置:database.php
- 路由和URL配置:route.php
- Cookie配置:cookie.php
- Session配置:session.php
- 命令行配置:console.php
- 多语言配置:lang.php
- 日志配置:log.php
- 页面Trace配置:trace.php
- 磁盘配置: filesystem.php
- 中间件配置:middleware.php
- 视图配置:view.php
- 改成用yaconf配置
- 事件
- 例子:省略事件类的demo
- 例子2:完整事件类
- 例子3:事件订阅,监听多个事件
- 解析
- 路由
- 路由定义
- 路由地址
- 变量规则
- MISS路由
- URL生成
- 闭包支持
- 路由参数
- 路由中间件
- 路由分组
- 资源路由
- 注解路由
- 路由绑定
- 域名路由
- 路由缓存
- 跨域路由
- 控制器
- 控制器定义
- 空控制器、空操作
- 空模块处理
- RESTFul资源控制器
- 控制器中间件
- 请求对象Request(url参数)
- 请求信息
- 获取输入变量($_POST、$_GET等)
- 请求类型的获取与伪装
- HTTP头信息
- 伪静态
- 参数绑定
- 请求缓存
- 响应对象Response
- 响应输出
- 响应参数
- 重定向
- 文件下载
- 错误页面的处理办法
- 应用公共文件common.php
- 模型
- 模型定义及常规属性
- 模型数据获取与模型赋值
- 查询
- 数据集
- 增加
- 修改
- 删除
- 条件
- 查询范围scope
- 获取器
- 修改器
- 搜索器
- 软删除
- 模型事件
- 关联预载入
- 模型关联
- 一对一关联
- 一对多关联
- 多对多关联
- 自动时间戳
- 事务
- 数据库
- 查询构造器
- 查询合集
- 子查询
- 聚合查询
- 时间查询
- 视图查询(比join简单)
- 获取查询参数
- 快捷方法
- 动态查询
- 条件查询
- 打印sql语句
- 增
- 删
- 改
- 查
- 链式操作
- 查询表达式
- 分页查询
- 原生查询
- JSON字段
- 链接数据库配置
- 分布式数据库
- 查询事件
- Db获取器
- 事务操作
- 存储过程
- Db数据集
- 数据库驱动
- 视图
- 模板
- 模板配置
- 模板位置
- 模板渲染
- 模板变量与赋值(assign)
- 模板输出替换
- url生成
- 模板详解
- 内置标签
- 三元运算
- 变量输出
- 函数输出
- Request请求参数
- 模板注释及原样输出
- 模板继承
- 模板布局
- 原生PHP
- 模板引擎
- 视图过滤
- 视图驱动
- 验证
- 验证进阶之最终版
- 错误和日志
- 异常处理
- 日志处理
- 调试
- 调试模式
- Trace调试
- SQL调试
- 变量调试
- 远程调试
- 杂项
- 缓存
- Session
- Cookie
- 多语言
- 上传
- 扩展说明
- N+1查询
- TP类库
- 扩展类库
- 数据库迁移工具
- Workerman
- think助手工具库
- 验证码
- Swoole
- request
- app
- Response
- View
- Validate
- Config
- 命令行
- 助手函数
- 升级指导(功能的添加与删除说明)
- siyucms
- 开始
- 添加页面流程
- 列表页加载流程
- 弹出框
- 基础控制器
- 基础模型
- 快速构建
- 表单form构建
- 表格table构建
- MakeBuilder
- 前端组件
- 日期组件
- layer 弹层组件
- Moment.js 日期处理插件
- siyucms模板布局
- 函数即其变量
- 前端页面
- $.operate.方法
- $.modal.方法:弹出层
- $.common.方法:通用方法
- 被cms重写的表格options
- 自定义模板
- 搜索框
- 自定义form表单
- 获取表单搜索参数并组装为url字符串