Tp5.1也引入了中间件的功能
中间件使用
1.定义中间件类
框架可以使用它命令或者自己在application/http/middleware
目录下面生成一个Check
中间件,格式一定要如下:必须有handle方法,第一个参数必须是Request对象,第二个是闭包。
class Check
{
public function handle(Request $request, \Closure $next)
{
$request->name = 'aaaaa';
return $next($request); //执行$next闭包函数,并且把这个闭包函数的结果return。
}
}
中间件的handle方法就是执行自己的代码,然后调用执行下一个中间件的代码。 结合后面的解析,我们知道$next就是通过Middleware::resolve()返回的闭包函数。这个闭包函数的执行就是从中间件队列中弹出一个中间件,然后通过call_fun_array()执行这个中间件的handle方法。所以中间件的return其实是执行最后一个中间件的return结果。
后置中间件
class After
{
public function handle(Request $request, \Closure $next)
{
$response = $next($request);
//todo 执行后置中间件的代码
return $response;
}
}
2.注册中间件
1.在路由上注册(只有这个路由才会走中间件)
传'Check',会自动在application/http/middleware目录下找到Check类,也可以传完整类名
Route::get('test', 'Demo/dbTest')->middleware('Check');
2.在对应的目录模块上注册 (这个目录下的控制器方法都会走中间件)tp5.1.8+
例如在application目录下新建middleware.php,则全局都会走这个中间件,如果在application\admin目录下建middleware.php,则只有admin模块下的控制器才会走中间件。
return [
'Check', //没有写类全名,默认在application/http/middleware下找中间件
];
3.在对应控制器注册中间件(只有这个控制器的方法才走)tp5.1.17+
控制器需要继承系统的think\Controller
类,然后在控制器中定义middleware
属性,例如
namespace app\index\controller;
use think\Controller;
class Index extends Controller
{
protected $middleware = ['check'];
public function test(){}
}
中间件原理 (Middleware类)
think\Middleware类重要属性
$queue = [],存放中间件的数组
$config = [],存放中间件的配置信息
think\Middleware类重要方法
1.buildMiddleware($middleware, $type = 'route'),$middleware可以是‘check’,check的类名,或者闭包。
//如果$middleware是闭包
if ($middleware instanceof \Closure) {
return [$middleware, isset($param) ? $param : null];
}
//如果是'check',会去拼接到全类名
return [[$this->app->make($middleware), 'handle'], isset($param) ? $param : null];
2.add()把解析好的中间件挂到属性$queue. 一个中间件元素的格式是[['中间件对象',‘handle’], $param];或者[闭包,$param];
$middleware = $this->buildMiddleware($middleware, $type);
if ($middleware) {
$this->queue[$type][] = $middleware;
}
并且以$type区分开,$type默认是‘route’;
3.dispatch(Request $request, $type = 'route')中间件调度,就是执行resolve返回的闭包函数
//就是按照一定顺序执行中间件
public function dispatch(Request $request, $type = 'route')
{
return call_user_func($this->resolve($type), $request);
}
4.resolve($type),返回闭包函数,这个闭包函数就是handle方法中的$next。
protected function resolve($type = 'route')
{
return function (Request $request) use ($type) {
$middleware = array_shift($this->queue[$type]);
list($call, $param) = $middleware;
try {
$response = call_user_func_array($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;
};
}
如何执行中间件的handle方法的,call_user_func_array(),最终得到某个中间件的返回值。并且返回值必须是Response对象,不然会包错。
//这个就是执行中间件的handle方法,而handler方法最后又会执行resolve返回的闭包方法,
//相当于又得到另外一个$call再次执行下面这段代码。所以这是一个递归的过程。
//一直循环到某个中间件,没有再调用$this->resolve($type)这个闭包参数。即在handle方法中没有执行$next($resquest);而是直接返回Response对象。然后一层一层往上归回来。
$response = call_user_func_array($call, [$request, $this->resolve($type), $param]);
$call就是[中间件对象,'handle']
$request就是handle的第一个参数
$this->resolve($type),就是handle的第二个参数,是一个闭包
$param是第三个参数
$response就是handle的返回值
TP5.1是如何使用Middleware类库实现中间件的功能的
1.初始化模块init(),会加载模块目录下的middleware.php文件
// 加载中间件文件,import没有传type,默认是route
if (is_file($path . 'middleware.php')) {
$middleware = include $path . 'middleware.php';
if (is_array($middleware)) {
$this->middleware->import($middleware);
}
}
初始化模块方法init(),在系统初始化的时候调用一次,那时候还没有解析路由,默认模块就是Application模块,然后根据访问路径得到模块,然后会再次调用,表示初始化对应模块。所以一个进程只会加载Application/和访问模块的下的middleware.php。
2. 路由文件导入,include rute目录下的文件。
//get方法返回的是RuleItem实例,我们知道一条路由规则生成一个RuleItem实例。
Route::get('test', 'Demo/test')->middleware('Check');
middleware方法会把Check放到属性option中,在创建调度对象Module实例前,会把中间件加进去
// 添加域名中间件
if (!empty($this->option['middleware'])) {
Container::get('middleware')->import($this->option['middleware']);
unset($this->option['middleware']);
}
3.调度器执行方法,作为一个匿名函数,加入到中间件队列中
//通过路由解析后,最终通过$dispatch->run()其实就是执行我们的控制器方法,然后返回Response对象。
//正常路由解析后,$data为null。
$this->middleware->add(function (Request $request, $next) use ($dispatch, $data) {
return is_null($data) ? $dispatch->run() : $data;
});
//中间件调度,把注册好的中间件按顺序执行,直到执行到返回Response对象的那个中间件
$response = $this->middleware->dispatch($this->request);
上面三种方式,分别把middleware.php的中间件,自定义路由的中间件,控制器的执行分别加入到中间件队列。
PHP的Closure类
我们创建一个闭包函数,var_dump这个闭包函数,发现他是一个Closure类。在PHP中定义一个闭包函数其实就是一个Closure类的实例。闭包函数作为函数参数,可以看做是把这段函数代码传给函数。