laravel auth 中间件中怎样给路由数组添加中间件

Laravel中间件实现原理有什么? _ 路由器设置|192.168.1.1|无线路由器设置|192.168.0.1 - 路饭网
您的位置: >
> 阅读资讯:Laravel中间件实现原理有什么?
Laravel中间件实现原理有什么?
Laravel中间件实现原理有什么?
本文实例讲述了Laravel的中间件实现原理。分享给大家供大家参考,具体如下:
#1 什么是中间件?
对于一个Web应用来说,在一个请求真正处理前,我们可能会对请求做各种各样的判断,然后才可以让它继续传递到更深层次中。而如果我们用if else这样子来,一旦需要判断的条件越来越来,会使得代码更加难以维护,系统间的耦合会增加,而中间件就可以解决这个问题。我们可以把这些判断独立出来做成中间件,可以很方便的过滤请求。
#2 Laravel中的中间件
在Laravel中,中间件的实现其实是依赖于Illuminate\Pipeline\Pipeline这个类实现的,我们先来看看触发中间件的代码。很简单,就是处理后把请求转交给一个闭包就可以继续传递了。
public function handle($request, Closure $next) {
//do something for $request
return $next($request);
#3 中间件内部实现
上面说道,中间件是靠Pipeline来实现的,它的调用在Illuminate\Routing\Router中
return (new Pipeline($this-&container))
-&send($request)
-&through($middleware)
-&then(function ($request) use ($route) {
return $this-&prepareResponse(
$route-&run($request)
可以看到,中间件执行过程调用了三个方法。再来看看这三个方法的代码:
public function send($passable){
$this-&passable = $
其实send方法没做什么事情,就是设置了需要在中间件中流水处理的对象,在这里就是HTTP请求实例。
through方法
public function through($pipes){
$this-&pipes = is_array($pipes) ? $pipes : func_get_args();
through方法也很简单,就是设置一下需要经过哪些中间件处理。
真正难懂的来了,then方法代码很简洁,但是要理解可不容易。
public function then(Closure $destination){
//then方法接受一个闭包作为参数,然后经过getInitialSlice包装,而getInitialSlice返回的其实也是一个闭包,如果还不知道什么是闭包先去看PHP文档
$firstSlice = $this-&getInitialSlice($destination);
//反转中间件数组,主要是利用了栈的特性,用处接下来再说
$pipes = array_reverse($this-&pipes);
//这个call_user_func先不要看,它其实就是执行了一个array_reduce返回的闭包
return call_user_func(
//接下来用array_reduce来用回调函数处理数组,建议先去PHP文档读懂array_reduce的执行原理。其实arrary_reduce什么事情都没干,就是包装闭包然后移交给call_user_func来执行
array_reduce($pipes, $this-&getSlice(), $firstSlice), $this-&passable
然后就没有然后了,这样就过完了所有中间件,是不是很优雅?
由于aray_reduce的第二个参数需要一个函数,我们这里重点看看getSlice()方法的源码
protected function getSlice(){
return function ($stack, $pipe) { //这里$stack
return function ($passable) use ($stack, $pipe) {
if ($pipe instanceof Closure) {
return call_user_func($pipe, $passable, $stack);
list($name, $parameters) = $this-&parsePipeString($pipe);
return call_user_func_array([$this-&container-&make($name), $this-&method],
array_merge([$passable, $stack], $parameters));
看到可能会很头晕,闭包返回闭包的。简化一下就是getSlice()返回一个函数A,而函数A又返回了函数B。为什么要返回两个函数呢?因为我们中间在传递过程中是用$next($request)来传递对象的,而$next($request)这样的写法就表示是执行了这个闭包,这个闭包就是函数A,然后返回函数B,可以给下一个中间件继续传递。
再来简化一下代码就是:
//这里的$stack其实就是闭包,第一次遍历的时候会传入$firstSlice这个闭包,以后每次都会传入下面的那个 而$pipe就是每一个中间件
array_reduce($pipes, function ($stack, $pipe) {
return function ($passable) use ($stack, $pipe) {
}, $firstSlice);
再来看这一段代码:
//判断是否为闭包,这里就是判断中间件形式是不是闭包,是的话直接执行并且传入$passable[请求实例]和$stack[传递给下一个中间件的闭包],并且返回
if ($pipe instanceof Closure) {
return call_user_func($pipe, $passable, $stack);
//不是闭包的时候就是形如这样Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode执行
//解析,把名称返回,这个$parameters看了许久源码还是看不懂,应该是和参数相关,不过不影响我们的分析
list($name, $parameters) = $this-&parsePipeString($pipe);
//从容器中解析出中间件实例并且执行handle方法
return call_user_func_array([$this-&container-&make($name), $this-&method],
//$passable就是请求实例,而$stack就是传递的闭包
array_merge([$passable, $stack], $parameters));
再看一张图片:
每一次迭代传入上一次的闭包和需要执行的中间件,由于反转了数组,基于栈先进后出的特性,所以中间件3第一个被包装,中间件1就在最外层了。要记得,arrary_reduce他不执行中间件代码,而是包装中间件。
看到这里应该明白了,array_reduce最后会返回func3,那么call_user_func(func3,$this-&passable)实际就是
复制代码 代码如下:
return call_user_func($middleware[0]-&handle, $this-&passable, func2);
而我们的中间件中的handle代码是:
public function handle($request, Closure $next) {
return $next($request);
这里就相当于return func2($request),这里的$request就是经过上一个中间件处理过的。所以正果中间件的过程就完了,理解起来会有点绕,只要记得最后是由最外面的call_user_func来执行中间件代码的
希望本文所述对大家基于Laravel框架的PHP程序设计有所帮助。
本文地址:
相关文章列表laravel中间件源码分析 - 给个理由先 - 博客园
laravel中间件源码分析
在中,HTTP 为过滤访问你的应用的 HTTP 请求提供了一个方便的机制。在处理逻辑之前,会通过中间件,且只有通过了中间件才会继续执行逻辑代码。它的主要作用就是过滤Http请求(php aritsan是没有中间件机制的),同时也让系统的层次(Http过滤层)更明确,使用起来也很优雅。但实现中间件的代码却很复杂,接下来就分析下有关中间件的源码(讨论是在laravel5.2上展开的)。
中间件源码
中间件本身分为两种,一种是所有http的,另一种则是针对route的。一个有中间件的请求周期是:Request得先经过Http中间件,才能进行Router,再经过Requset所对应Route的Route中间件, 最后才会进入相应的Controller代码。laravel把请求分为了两种:http和console。不同的请求方式用它自己的Kernel来驱动Application。Http请求则是通过
\Illuminate\Foundation\Http\Kernel类来驱动,它定义了所有的中间件,其父类\Illuminate\Foundation\Http\Kernel::handle就是对请求进行处理的入口了
Http中间件
跟踪入口handle()方法,很容易发现该函数(\Illuminate\Foundation\Http\Kernel::sendRequestThroughRouter):
protected function sendRequestThroughRouter($request)
$this-&app-&instance('request', $request);
Facade::clearResolvedInstance('request');
$this-&bootstrap();
return (new Pipeline($this-&app))
-&send($request)
-&through($this-&app-&shouldSkipMiddleware() ? [] : $this-&middleware)
-&then($this-&dispatchToRouter());
该函数会把Requset分发到Router(通过方法名就知道了), 主要的逻辑则是通过\Illuminate\Routing\Pipeline完成的, 作用就是让Requset通过Http中间件的检测,然后再到达Router。这里的代码看起来很优雅,但不是很好理解。所以,了解Pipeline的运行机制就会明白中间件的使用。
Pipeline的运行实现
Pipleline基类是\Illuminate\Pipeline\Pipeline,它的执行在then方法:
public function then(Closure $destination)
$firstSlice = $this-&getInitialSlice($destination);
$pipes = array_reverse($this-&pipes);
return call_user_func(
array_reduce($pipes, $this-&getSlice(), $firstSlice), $this-&passable
了解这段代码执行的意图,必须要知道做了什么。 为了清楚array_reduce怎么运行的,先把array_reduce重写一次:
//将数组中的元素,依次执行$func函数,且上一次的$func的返回值作为下一次调用$func的第一个参数输入
function array_reduce_back($arr, callable $func, $firstResult = null)
$result = $firstR
foreach ($arr as $v) {
$result = $func($result, $v);
所以,源代码中的$func是getSlice(),它返回的是一个回调函数:function($passable) use ($stack, $pipe){...}($stack和$pipe被输入的具体值代替),也就是说作为上一次返回结果输入到下一次$func的第一个参数是上述的回调函数,如此循环,当数组遍历完成,array_reduce就返回的是一个回调函数,现在关键就是了解这个回调函数是什么样子,又如何执行?为方便讨论,可分析下面的代码:
call_user_func(
array_reduce([1, 2, 3], $this-&getSlice(), $firstSlice), $this-&passable
执行说明:
1.$result_0是初始化的值 ,为$firstSlice ,即是\Illuminate\Pipeline\Pipeline::getInitialSlice的返回回调
2.每遍历一个元素,都会执行\Illuminate\Pipeline\Pipeline::getSlice的回调,同时也会返回一个回调
3.$result中的具体执行代码都在getSlice()中
4.最后的array_reduce返回结果是$result_3,是一个有多层闭包的回调函数
5.执行的是call_user_func($result_3, $this-&passable),即执行function($this-&passable) use ($result_2, 3){...}
至此已经清楚了then()是如何运行的了,要继续下去,则需再搞定回调函数到底怎么执行的.现在再跟着sendRequestThroughRouter中的Pipeline走,看它是如何执行的。
// 把具体的参数带进来
return (new Pipeline($this-&app))
-&send($request)
-&through(['\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode'])
-&then($this-&dispatchToRouter());
用上面的所分析的Pipeline执行过程,很快就会分析出最后执行的是
function($requset) use (\Illuminate\Foundation\Http\Kernel::dispatchToRouter(), '\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode') {
if ($pipe instanceof Closure) {
return call_user_func($pipe, $passable, $stack);
// $name和$parameters很容易得到
// $name = '\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode';
// $parameters = [];
list($name, $parameters) = $this-&parsePipeString($pipe);
// 执行的就是\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::handle($request, \Illuminate\Foundation\Http\Kernel::dispatchToRouter())
return call_user_func_array([$this-&container-&make($name), $this-&method],
array_merge([$passable, $stack], $parameters));
逻辑处理已经到了\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::handle,其代码是:
public function handle($request, Closure $next)
if ($this-&app-&isDownForMaintenance()) {
throw new HttpException(503);
return $next($request);
这里,它处理了这个中间件所需要过滤的条件,同时执行了$next($request),即\Illuminate\Foundation\Http\Kernel::dispatchToRouter(), 这样,就把Request转到了Router中,也就完成了Http中间件的所有处理工作,而$next($request)是每个中间件都不可少的操作,因为在回调中嵌套了回调,就是靠中间件把Request传递到下一个回调中,也就会解析到下一个中间件,直到最后一个。紧跟上面的已分析的Pipeline执行过程,讲其补充完整:
6.执行$result_3中的回调,getSlice实例化中间件,执行其handle,在中间件处理中执行回调
7.回调中还嵌套回调的,每个中间件中都需有执行回调的代码$next($request) ,才能保证回调中的回调会执行,执行的顺序就是3::handel,2::handel,1::handel,$first
8.最里面一层,一定是传递给then()的参数,then执行的就是最后一步
9.执行的顺序是由数组中的最后一个,向前,到then()的参数,为了使其执行顺序是数组中的第一个到最后一个,再到then()中的参数,then()方法中就做了一个反转array_reverse
Pipeline小结
现在,Pipeline的所有执行流程就都分析完了。实现代码真的很绕,但理解之后编写自定义的中间件应该就很容易了。现在再把Pipeline的使用翻译成汉语,应该是这样的
// 使用管道,发送$request,使之通过middleware ,再到$func
(new Pipeline($this-&app))-&send($request)-&through($this-&middleware)-&then($func);
这样的代码不管是从语义上,还是使用上都很优雅,高!确实是高!再回到源码,Requset的流程就通过dispatchToRouter进入到了Router
Route中间件
在Router中,\Illuminate\Routing\Router::dispatch就承接了来自Http中间件的Requset, Router把Request分发到了具体的Route,再进行处理,主要代码如下:
public function dispatchToRoute(Request $request)
// 找到具体的路由对象,过程略
$route = $this-&findRoute($request);
$request-&setRouteResolver(function () use ($route) {
// 执行Request匹配到Route的事件,具体的代码在这里:\Illuminate\Foundation\Providers\FoundationServiceProvider::configureFormRequests
$this-&events-&fire(new Events\RouteMatched($route, $request));
// 这里就运行路由中间件了
$response = $this-&runRouteWithinStack($route, $request);
return $this-&prepareResponse($request, $response);
protected function runRouteWithinStack(Route $route, Request $request)
// 获取该路由上的中间件
// 简单就点可这样写:
// $middleware = App::shouldSkipMiddleware() ? [] : $this-&gatherRouteMiddlewares($route);
$shouldSkipMiddleware = $this-&container-&bound('middleware.disable') &&
$this-&container-&make('middleware.disable') ===
$middleware = $shouldSkipMiddleware ? [] : $this-&gatherRouteMiddlewares($route);
// 了解Pipeline后,这里就好理解了,应该是通过管道,发送$request,经过$middleware,再到then中的回调
return (new Pipeline($this-&container))
-&send($request)
-&through($middleware)
-&then(function ($request) use ($route) {
return $this-&prepareResponse(
$route-&run($request)
如何获取Route中间件的,就可以跟gatherRouteMiddlewares,这个代码并不难,很好跟。接下来,Request就到到达至于Controller, Request是如何到达Controller的代码就不难了,这里就不说了
Controller后执行中间件
成功获取Response后,在public/index.php58行执行了$kernel-&terminate($request, $response);, 也就是在主要逻辑处理完成之后,再执行此代码,它实际上调用是的\Illuminate\Foundation\Http\Kernel::terminate, 跟进去就很容易发现,它处理了这此请求所涉及到的中间件,并执行了各自的terminate方法,到这里,中间件的另一个功能就展现出来了,就是主要逻辑完成之后的收尾工作.到这里为止,中间件就完成了它的使命(一个请求也就完成了).
如何使用中间件
在官方文档上讲解的很清楚
中间件小结
至此,中间件的实现逻辑与使用就清晰了.从执行的顺序来分,一个在Controller之前,一个在Controller之后,所以它一个很重要的作用就是可以让Controller专注于自己的主要逻辑的职责更明确. 奇怪的是,但前后两种中间件的执行方式却不一样, \Illuminate\Foundation\Http\Kernel::terminate,中间件的结束却没有使用Pipeline, 而是直接foreach.相同的工作却用两种代码来实现.现在看来,中间件本身并不复杂,但它带给了我两个启发,1.层次明确 2,Pipeline所带来的优雅.
下面是我学习过程中的一些资源Laravel(3)
我们知道,路由是一个过程,分析来自客户端的请求按照路由规则分发至相应的处理逻辑。但有种情况,打个比方:后台。后台不是所有人都能访问的,我们在正式的处理逻辑前,需要做一个验证,比如验证是否具有权限或者请求的数据是否合法。
这时候,路由过程的一部分——中间件就上场了。
app/Http/Kernel.php中的 $middleware 数组是全局中间件,也就是说,任何一条路由都会被应用这些中间件,比如里面的CSRF验证中间件。
有时候我们不需要全局中间件,这时候可以将某一个中间件注册至app/Http/Kernel.php文件中的$routeMiddleware数组,数组的键名是中间件的别名,键值是具体的中间件类,如
'auth' =& 'App\Http\Middleware\AuthMiddleware'。
具体如何在某一路由上使用特定的中间件我们下文继续。
我们在app/Http/Kernel.php文件中的$routeMiddleware数组注册了一个独立中间件,这一中间件可被单独用绑定在一个路由和路由组上。在路由定义的时候可以像这样:
Route::get('admin/profile', ['middleware' =& 'auth', function()
}]);当我们访问http://yourdomain/admin/profile的时候,首先会经过全局中间件,然后就是我们在app/Http/Kernel.php的$routeMiddleware数组中定义的名称为auth的中间件。
路由组群往往适用于给某一类路由分组,给这个路由组分配的中间件、过滤器等,都会被运用到该组内的所有路由。
说白了,路由组就是简化一部分路由定义过程的。比如,后台的我都想通过地址http://yourdomain/admin/***访问,假如我有用户(user)、文章(article)两个模块,他们的访问都要经过一个验证权限的中间件,我需要这样定义路由:
Route::get('admin/user', ['middleware' =& 'authority', function() {
// blablabla...
Route::get('admin/article', ['middleware' =& 'authority', function() {
// blablabla...
现在只有两条路由,我多写几个admin,middleware没啥的,但系统庞大以后,每个都要单独写对应的中间件,容易出错,不易管理。这时候,就应该使用路由组:
Route::group(['prefix' =& 'admin', 'middleware' =& 'authority'], function() {
Route::get('user', function() {
// blablabla...
Route::get('article', function() {
// blablabla...
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:7346次
排名:千里之外
原创:38篇
转载:31篇
(15)(3)(13)(5)(5)(3)(3)(2)(3)(1)(7)(2)(1)(4)(2)(1)(1)

我要回帖

更多关于 laravel 中间件使用 的文章

 

随机推荐