thinkphp笔记-中间件
定义中间件
中间件的主要用于拦截和过滤HTTP 请求,并进行相应处理;对HTTP提交过来的各种请求的值进行处理和判断。这些请求的功能可以是URL 重定向、权限验证等等;
可以通过命令行模式,在应用目录下生成一个中间件文件和文件夹;
// cmd在项目文件夹中执行
\xuexi\tp6>php think make:middleware Check
成功后,会多出一个app/middleware/Check.php目录和文件,Check.php默认的文件内容如下
<?php
declare (strict_types = 1);
namespace app\middleware;
class Check
{
/**
* 处理请求
*
* @param \think\Request $request
* @param \Closure $next
* @return Response
*/
public function handle($request, \Closure $next)
{
//
}
}
<?php
// 全局中间件定义文件
return [
// 全局请求缓存
// \think\middleware\CheckRequestCache::class,
// 多语言加载
\think\middleware\LoadLangPack::class,
// Session初始化
\think\middleware\SessionInit::class,
// 注册一个自己的中间件
\app\middleware\Check::class
];
中间件基本结构分析
中间件的入口执行方法必须是:handle()方法,第一参数请求(依赖注入),第二参数是闭包(是为了让回调函数返回response对象);而response对象的作用就是让程序在执行了中间件之后,再继续去执行后面的主体程序。
只要创建并注册了中间件,那么他就会自动、全局强制执行。因此,如果按照文件内容,运行程序就会报错 “中间件方法必须返回Response对象实例”,为了测试拦截后,无法继续执行,可以return response()助手函数测试;
<?php
declare (strict_types = 1);
namespace app\middleware;
class Check
{
/**
* 处理请求
*
* @param \think\Request $request
* @param \Closure $next
* @return Response
*/
public function handle($request, \Closure $next)
{
// 直接返回空对象可以屏蔽掉无返回的错误
return response();
}
}
假如控制器中加载了一个模板,
public function test_middleware()
{
echo '主体程序';
return View::fetch('middleware');
}
但是会发现这样拦截之后,中间件会中断流程,导致模板不被加载,所以应该采用下面的固定写法:
public function handle($request, \Closure $next)
{
// 固定写法,不阻拦流程的执行
return $next($request);
}
$next($request); 这里回调本身会返回response对象
然后加一个拦截的条件判断
public function handle($request, \Closure $next)
{
// 处理HTTP请求,中间件代码
// 如果查询参数中的name属性==index,那么就跳转到首页
if($request->param('name') == 'index') {
return redirect('../');
}
// 固定写法,不阻拦流程的执行
return $next($request);
}
测试访问:http://localhost/xuexi/tp6/public/store/middleware?name=wu 如果请求的name 是wu,那需要继续往下执行控制器中的主体程序才行,不能被拦死;(但是仅仅返回一个空的response对象是没有用的,因为空的response对象里面没有要执行的代码)页面不会跳转,会正常渲染模板;
http://localhost/xuexi/tp6/public/store/middleware?name=index,中间件就会拦截请求,跳转到首页
前/后置中间件
将$next($request)放在方法底部的方式,属于前置中间件;
前置中间件就是请求阶段来进行拦截验证,比如登录判断、跳转、权限等;在执行主体代码之前,先做一系列的验证。
而后置中间件就是等待中间件和主体程序执行完毕之后再进行验证,比如写入日志等等;
下面是一个前置中间件与后置中间件 执行位置
public function handle($request, \Closure $next)
{
// 前置中间件执行区域
echo '前置中间件';
$response = $next($request);
// 后置中间件执行区域
echo '后置中间件';
return $response;
}
如果分开的话
前置中间件
echo '前置中间件';
return $next($request);
后置中间件
$response = $next($request);
echo '后置中间件';
return $response;
结束调度
中间件提供了一个end()方法,可以在中间件执行到最后时执行; 就是无论前置、后置、主体程序、模板加载等待都执行完毕后,就会执行end()钩子。
<?php
declare (strict_types = 1);
namespace app\middleware;
use think\Response;
class Check
{
/**
* 处理请求
*
* @param \think\Request $request
* @param \Closure $next
* @return Response
*/
public function handle($request, \Closure $next)
{
// 直接返回空对象可以屏蔽掉无返回的错误
// return response();
// 处理HTTP请求
// 如果查询参数中的name属性==index,那么就跳转到首页
// if($request->param('name') == 'index') {
// return redirect('../');
// }
// 前置中间件执行区域
echo '前置中间件';
$response = $next($request);
// 后置中间件执行区域
echo '后置中间件';
// 固定写法,不阻拦流程的执行
return $response;
}
public function end(Response $response)
{
echo '结束';
}
}
end钩子也是自动执行,属于收尾工作了。
路由中间件
创建一个给路由使用的中间件,判断路由的ID 值实现相应的验证;
首先在项目目录下启动服务
php think run
这时候需要将app/middleware.php中,上面新增的全局中间件先注释掉
app/middleware.php这个文件是注册全局中间件的!
// 全局中间件定义文件
return [
// 全局请求缓存
// \think\middleware\CheckRequestCache::class,
// 多语言加载
\think\middleware\LoadLangPack::class,
// Session初始化
\think\middleware\SessionInit::class,
// 注释掉这个
// \app\middleware\Check::class
];
然后在controller/Address.php类中 新写一个控制器
class Address {
public function read($id)
{
return 'id' . $id;
}
}
然后新建并在app/middleware/Auth.php中
<?php
declare (strict_types = 1);
namespace app\middleware;
use think\Response;
class Auth
{
/**
* 处理请求
*
* @param \think\Request $request
* @param \Closure $next
* @return Response
*/
public function handle($request, \Closure $next)
{
if($request->param('id') == 10) {
echo '管理员';
}
$response = $next($request);
// 固定写法,不阻拦流程的执行
return $response;
}
}
判断当传入的参数Id=10的时候,打印一句话。
路由方法提供了一个middleware()方法,让指定的路由采用指定的中间件;
最后在根目录下的route/app.php中,注册一个路由,并指向AUth
Route::rule('ar/:id', 'Address/read')
->middleware(\app\middleware\Auth::class)
;
http://127.0.0.1:8000/ar/10 会输出:管理员id10
当然在路由中使用中间件也支持引用模式:
use app\middleware\Check;
use app\middleware\Auth;
// 如果是引用的话 就不需要前面加\app之类的了
Route::rule('ar/:id', 'Address/read')
->middleware(Auth::class)
;
也支持添加多个中间件,使用数组传入
// 也支持添加多个中间件
Route::rule('ar/:id', 'Address/read')
->middleware([ Auth::class, Check::class ])
;
也可以在根目录下的config/middleware.php 配置文件加中,配置别名支持(config与app目录同级);
<?php
// 中间件配置
return [
// 别名或分组 默认alias这里是空数组
'alias' => [
'Auth' => \app\middleware\Auth::class,
'Check' => \app\middleware\Check::class
],
// 优先级设置,此数组中的中间件会按照数组中的顺序优先执行
'priority' => [],
];
设置好之后,在route/app.php
Route::rule('ar/:id', 'Address/read')
->middleware([ 'Auth', 'Check' ])
;
可以给中间件传递额外参数,通过中间件入口方法的第三个参数接收;
在route/app.php中,给->middleware传入第二个参数
Route::rule('ar/:id', 'Address/read')
->middleware([ 'Auth', 'Check' ], 'ok')
;
那么该如何接收这个参数?在app/middleware/Auth.php中
public function handle($request, \Closure $next, $param)
{
if($request->param('id') == 10) {
echo '管理员' . $param;
}
$response = $next($request);
// 固定写法,不阻拦流程的执行
return $response;
}
这里hanlde可以接收第三个参数,这个参数就是接收从路由中->middleware方法传递来的第二个参数的数据的。
中间件也支持分组路由,闭包路由等;
// 分组路由
Route::group('ar', function () {
Route::rule(':id', 'Address/read')
})->middleware(Auth::class);
闭包路由,因为通常使用的时候,不值得为了简单的一个功能需求,就单独再在app/middleware/中新建单独的一个类,那么这时候就可以考虑使用闭包中间件,直接将中间件的相关操作写在闭包中:
Route::rule('ar/:id', 'Address/read')
->middleware(function($request, \Closure $next) {
if($request->param('id') == 10) {
echo '管理员' ;
}
// 固定写法,不阻拦流程的执行
return $next($request);
})
;
注意,闭包中function的传参,与单独类中hanle中的参数一样,都必须是$request, \Closure$next,这是固定写法!
控制器中间件
比如我这个中间件不是在全局做,而只是在某个控制器中使用。
可以让中间件在控制器里注册,让这个控制器执行的时候执行中间件;
class Address
{
// 只需要在开头加这么一段
protected $middleware = ['Check'];
public function read($id)
{
return 'id' . $id;
}
}
注意,上面这个写法依赖在config/middleware.php中配置依赖alias=》[..],(就像上文那样配置),如果不提前配置依赖
class Address
{
// 如果配置了依赖
// protected $middleware = ['Check'];
// 如果不配置依赖就使用下面这种方式
protected $middleware = [ \app\middleware\Check::class ];
public function read($id)
{
return 'id' . $id;
}
}
如此配置后,这个控制器中无论执行/address/read/id/10、或者/address/index 等等方法,都会执行这个引入的中间件。会针对这个控制器的全局执行。
默认情况下,控制器中间件对所有操作方法有效,支持做限制;
如果我只要对这个address控制器中某些方法有效
protected $middleware = [
'Auth' => ['only' =>['index']], // 只对哪些方法有效
'Check' => ['except' =>['read']], // 对哪些方法无效,排除哪些方法
];
以上也是针对已经设置的依赖的情况,如果没设置依赖,那么还是使用\app引入或者在文件头use..即可
中间件给控制器传递参数,通过Request 对象实现;
我在app/middleware/Check.php文件中,向控制器address中传参
Check.php
public function handle($request, \Closure $next) {
// 通过中间件向控制器传参
$request->name = 'wubin';
// 固定写法,不阻拦流程的执行
return $next($request);
}
在控制器Address.php中,接收参数
namespace app\controller;
use app\Request; // 通过request传递接收参数
class Address
{
public function read(Request $request, $id)
{
echo $request->name;
return 'id' . $id;
}
}