Laravel中的中间件是laravel中的一个重点,本篇将从源码的角度去讲解Lravel中的中间件,洞察Laravel中的中间件是如何运行的,明白为什么咱们使用中间件的时候要进行那些步骤. 本篇文章假设读者已经掌握中间件的基本用法,若是不了解其用法,能够移步查看laravel中间件的使用php
咱们都知道,使用Laravel中间件有三个步骤:linux
使用php artisan
生成一个中间件,这里假设生成一个TestMiddleware
的中间件laravel
重写TestMiddleware
中的handle
函数,其中代码逻辑写在return $next($request);
以前或者以后表示在执行请求以前或者以后运行这段代码.bootstrap
在app/Http/Kernel.php
的routeMiddleware
注册一个中间件数组
然而,这上面的几点下来,你会不会一头雾水,为何要执行这么些操做以后才能使用一个中间件.尤为是第二点?cookie
你必定听过Laravel中间件的概念跟装饰器模式很像.简单来说,装饰器模式就是在开放-关闭原则下动态的增长或者删除某一个功能.而Laravel的中间件也差很少是这个道理:session
一个请求过来,在执行请求以前,可能要进行Cookie加密,开启回话,CSRF保护等等操做.可是每个请求不必定都须要这些操做,并且,在执行请求以后也可能须要执行一些操做.咱们须要根据请求的特性动态的增长一些操做.这些需求正好可使用装饰器模式解决.闭包
可是,Laravel中的中间件在代码实现上跟中间件 又有点区别,这里给出一段代码.真实的模拟了Laravel中间件的工做流程.app
<?php /** * Created by PhpStorm. * User: 89745 * Date: 2016/12/4 * Time: 13:56 */ interface Milldeware { public static function handle(Closure $next); } class VerfiyCsrfToekn implements Milldeware { public static function handle(Closure $next) { echo '验证csrf Token <br>'; $next(); } } class ShowErrorsFromSession implements Milldeware { public static function handle(Closure $next) { echo '共享session中的Error变量 <br>'; $next(); } } class StartSession implements Milldeware { public static function handle(Closure $next) { echo '开启session <br>'; $next(); echo '关闭ession <br>'; } } class AddQueuedCookieToResponse implements Milldeware { public static function handle(Closure $next) { $next(); echo '添加下一次请求须要的cookie <br>'; } } class EncryptCookies implements Milldeware { public static function handle(Closure $next) { echo '解密cookie <br>'; $next(); echo '加密cookie <br>'; } } class CheckForMaintenacceMode implements Milldeware { public static function handle(Closure $next) { echo '肯定当前程序是否处于维护状态 <br>'; $next(); } } function getSlice() { return function($stack,$pipe) { return function() use($stack,$pipe){ return $pipe::handle($stack); }; }; } function then() { $pipe = [ 'CheckForMaintenacceMode', 'EncryptCookies', 'AddQueuedCookieToResponse', 'StartSession', 'ShowErrorsFromSession', 'VerfiyCsrfToekn' ]; $firstSlice = function() { echo '请求向路由传递,返回相应 <br>'; }; $pipe = array_reverse($pipe); $callback = array_reduce($pipe,getSlice(),$firstSlice); call_user_func($callback); } then();
运行代码,输出函数
肯定当前程序是否处于维护状态 解密cookie 开启session 共享session中的Error变量 验证csrf Token 请求向路由传递,返回相应 关闭ession 添加下一次请求须要的cookie 加密cookie
这段代码可能有点难懂,缘由在于对于闭包函数(Closure),array_reduce
以及call_user_fun
函数,并且函数调用过程又是递归,能够尝试使用xdebug来调试执行.这里只提一点,array_reduce
中第二个参数是一个函数,这个函数须要两个参数:
第一个参数从array_reduce
的第一个参数$pipe
数组中得到
第二个参数为上一次调用的返回值.这个例子里面返回值是一个闭包函数.
若是仍是不懂能够看这个例子.当理解这段代码以后,你会发现使用Laravel中间件步骤中的第2步瞬间就明白了.
好了,经过上面的代码,咱们已经解决了第二个问题.如今看看为何使用中间件以前须要在app/Http/Kernel.php
注册中间件.
咱们知道,所谓注册,也只是在$routeMiddleware
数组中添加一项而已.
要想知道,为何中间件注册完以后就可使用,咱们须要从源码的角度去看看Laravel在底层为咱们作了什么.
从Index.php文件分析,这里咱们不分析Laravel源码的所有,只讲解有关中间件的部分,关于Laravel的分析,请关注个人其余文章.而且,这里只讲解全局中间件的运行过程,由于路由中间件和全局中间件的运行流程是同样的,可是路由中间件.涉及到路由分发过程,本次分析只专一于中间件,等到讲解路由的时候再对路由中间件进行展开分析.
有关中间件的步骤从Index.php的handle函数开始.
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); $response = $kernel->handle( $request = Illuminate\Http\Request::capture() );
从这个函数开始处理一个请求,注意这里kernel有一个继承链,handle函数的真正实如今vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php
文件中.
转到Http/Kernel.php
类,在看handle
函数以前,咱们发现这类有两个protected
的成员:$middleware
跟$routeMiddleware
.看到这个咱们就能够联想到咱们注册中间件的那个文件app/Http/Kernel.php
,这个文件正是继承Illuminate/Foundation/Http/Kernel.php
的,因此咱们明白了,当咱们在app/Http/Kernel.php
注册的全局中间件会在这里被处理.
接着,咱们看handle函数,调用了sendRequestThroughRouter
函数,进入这个函数,咱们看到其函数体
{ $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()); }
能够看到这你实例化了一个Pipeline
.这个能够称之为管道,若是懂得linux中的管道概念的话,那么就能够理解这里命名为Pipeline
的缘由:客户端发过来的请求被一个又一个的中间件处理,前一个中间件处理往以后的结果交给了下一个中间价,相似管道同样.
pipeline
以后调用了三个函数send, through, then
.这三个函数分别作了
传递客户端请求request
到Pipeline
对象
传递在app/Http/Kernel.php
中定义的全局中间件到Pipeline
对象
执行中间件,其中then函数的参数,$this->dispatchToRouter()
返回的是一个回调函数,这个函数能够类比为咱们示例代码中输出请求向路由传递,返回相应
的函数, 由于这里涉及到路由的工做流程,因此暂时这么理解,等到了分析路由的时候,咱们再综合起来.
接下来,咱们来看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 ); }
发现then函数的代码跟咱们上面的示例代码有点相似,其中 :
$pipe
就是保存了在app/Http/Kernel.php
中定义的全局中间件,具体逻辑能够看through
函数
$this->passable
中保存的是客户端请求的实例对象requset
.具体逻辑能够从send
函数看到
而getInitialSlice
调用的函数只是对原有的destination
添加了一个$passable
的参数.这个$passabel
就是请求实例.
protected function getInitialSlice(Closure $destination) { return function ($passable) use ($destination) { return call_user_func($destination, $passable); }; }
了解了then函数里的全部信息, 下面执行的操做就跟咱们上面的示例代码同样了,只要理解了上面代码的逻辑,这里Laravel的代码也是这么工做的,惟一不一样的地方在于getSlice
返回的闭包函数,从上面的例子能够知道返回的闭包函数才是调用中间件的核心,咱们来看下getSlice
究竟是怎么工做的
protected function getSlice() { return function ($stack, $pipe) { return function ($passable) use ($stack, $pipe) { // If the pipe is an instance of a Closure, we will just call it directly but // otherwise we'll resolve the pipes out of the container and call it with // the appropriate method and arguments, returning the results back out. if ($pipe instanceof Closure) { return call_user_func($pipe, $passable, $stack); } else { list($name, $parameters) = $this->parsePipeString($pipe); return call_user_func_array([$this->container->make($name), $this->method], array_merge([$passable, $stack], $parameters)); } }; }; }
getSlice
函数的大致逻辑跟咱们上面实例代码的逻辑差很少,只是咱们上面调用在中间件的handle函数的时候直接使用$pipe::handle($stack);
,由于中间件里面的函数是静态函数.而在Laravel中,这里咱们只是传递了要实例化的中间件的类名,因此在getSlice
里面还要去实例化每一个要执行的中间件,
list($name, $parameters) = $this->parsePipeString($pipe); return call_user_func_array([$this->container->make($name), $this->method], array_merge([$passable, $stack], $parameters));
上面两句代码中,第一句根据中间件的类名去分离出要实例化的中间件类,和实例化中间件可能须要的参数,
而后call_user_func_array
里面因为柔和了几行代码,因此这里分解一下,函数包含两个参数 :
[$this->container->make($name), $this->method]
为调用某个类中方法的写法,其中$this->container->make($name)
是使用服务容器去实例化要调用的中间件对象,$this->method
就是handle函数
array_merge([$passable, $stack], $parameters)
为调用中间件所须要的参数,这里咱们能够看到调用中间件的handle必然会传递两个参数:$passable
(请求实例$request)和下一个中间件的回调函数$stack
到这里,Laravel中间件的部分就结束了,这部分代码有点难以理解,尤为是一些具备函数式特性的函数调用,好比array_reduce
,以及大量的闭包函数和递归调用,你们必定要耐心分析,能够多使用dd
函数和xdebug
工具来逐步分析.
这里扯一句,函数then
里面能够看到有getSlice
和$firstSlice
的命名,这里slice
是一片的意思,这里这样子的命名方式是一种比喻 : 中间件的处理过程就上面讲的相似管道,处理中间件的过程比做剥洋葱,一个中间件的执行过程就是剥一片洋葱.