laravel中间件的建立思路分析

网上有不少解析laravel中间件的实现原理,可是不知道有没有读者在读的时候不明白,做者是怎么想到要用array_reduce函数的?php

本文从本身的角度出发,模拟了若是我是做者,我是怎么实现这个中间件功能,又是怎么找到并使用对应的函数。laravel

 

什么是laravel中间件数组

Laravel 中间件提供了一种机制在不修改逻辑代码的状况下,中断本来程序流程,经过中间件来处理一些事件,或者扩展一些功能。好比日志中间件能够方便的记录请求和响应日志,而不须要去更改逻辑代码。闭包

那么咱们简化一下软件执行过程,如今有一个核心类kernel,下面是它的laravel代码框架

#捕获请求

$request = Illuminate\Http\Request::capture()

#处理请求

$response = $kernel->handle($request);

  

代码的做用是 捕获一个 Request ,返回一个 Response。这里面就是后续分发到具体执行逻辑的代码段并返回结果。函数

那么若是想在执行这个$kernel->handle()方法以前或者以后,增长一段逻辑通常会怎么写呢。大概以下:spa

$request = Illuminate\Http\Request::capture()

function midware(){

    before()#在以前执行的语句集合

    #####    

    $response = $kernel->handle($request);

    #####

    after()#在以后执行的语句集合

}

  

显然这样写没有问题,可是毫无拓展性可言,想执行什么东西都要更改这个方法,这种是不可能封装成框架核心内容的。怎么改进呢日志

定义一个要执行的中间件类叫middleware,类实现两个方法,before()和after()而后代码以下。csrf

#配置项中有一项配置中间件:

middleware = '';

$request = Illuminate\Http\Request::capture()

function midware(){

    middleware.before()

    #####    

    $response = $kernel->handle($request);

    #####

    middleware.after()

}

  

是否解决了问题呢,是解决了不用更改的问题,可是咱们若是须要多个中间件怎么办呢,最容易想到的就是:定义一个中间件数组middleware_arr,每个middleware类都含有before和after方法,代码以下:中间件

#配置项中有middleware_arr

middleware_arr=array();

$request = Illuminate\Http\Request::capture()

function midware(){

    foreach(middleware_arr as middleware){

       middleware.before()

    }

    #####    

    $response = $kernel->handle($request);

    #####

    foreach(middleware_arr as middleware){

        middleware.after()

    }

}

  

虽然有点老土,可是的确解决了问题。可是这个还存在一个问题,就是咱们怎么向中间件传递参数的问题,那么以下能够吗:

$request = Illuminate\Http\Request::capture()

function midware(){

    foreach(middleware_arr as middleware){

       middleware.before($request)

    }

    #####    

    $response = $kernel->handle($request);

    #####

    foreach(middleware_arr as middleware){

        middleware.after($response)

    }

}

  

看似是解决了问题,可是仔细分析,就会发现,这里面每次给中间件的都是最初的$request,这显然不行,修改为以下:

$request = Illuminate\Http\Request::capture()

function midware(){

    foreach(middleware_arr as middleware){

       $request = middleware.before($request)

    }

    #####    

    $response = $kernel->handle($request);

    #####

    foreach(middleware_arr as middleware){

        $response = middleware.after($response)

    }

}

  

还有一个问题就是,假设有两个中间件A和B,那么执行顺序应该是怎么样呢:
$request = Illuminate\Http\Request::capture()

$request = A.before($request);

$request = B.before($request);

$response = $kernel->handle($request);

$response = A.after();

$response = B.after();

  

这样合理吗?不太好分辨,咱们假设有一个记录请求和响应日志的中间件,这个时候,不论你把它放在什么位置,都不能完美的记录最初请求和最终日志。难道相似状况要写两个类,一个记录请求放在中间件数组第一个,一个处理响应,放在数组最后一位吗?不如在执行后面的foreach以前把middleware_arr数组给反转一下,这样就符合了要求:

$request = Illuminate\Http\Request::capture()

$request = A.before($request);

$request = B.before($request);

$response = $kernel->handle($request);

$response = B.after();

$response = A.after();

  

可是我也开始怀疑这个老土且不灵活的方案是否有更好的解决办法,在观察这个执行顺序的时候,发现是一个包裹样式(洋葱式)的。那个接下来的问题就能不能找到更灵活精美的解决方案,看上面这种结构,总感受有点熟悉,他很像是A的函数包裹B的函数,B的函数包括了最初的执行代码。函数内部调用函数容易,可是我们这里每个中间件之间是不知道对方存在的,因此要把其余中间件要执行的函数传递到上一级,这里就用到了闭包函数还有一个php函数array_reduce(),

array_reduce函数定义:mixed array_reduce ( array $input , callable $function [, mixed $initial = NULL ] )

<?php

 function  rsum ( $v ,  $w )

{

     $v  +=  $w ;

    return  $v ;

}

function  rmul ( $v ,  $w )

{

     $v  *=  $w ;

    return  $v ;

}

 $a  = array( 1 ,  2 ,  3 ,  4 ,  5 );

 $x  = array();

 $b  =  array_reduce ( $a ,  "rsum" );

 $c  =  array_reduce ( $a ,  "rmul" ,  10 );

 ?>   

 #输出:

这将使 $b  的值为 15, $c  的值为 1200(= 10*1*2*3*4*5)

  

array_reduce() 将回调函数 function 迭代地做用到 input 数组中的每个单元中,从而将数组简化为单一的值。我们是把多个函数包裹成最终调用一个函数。

#咱们先假设只有一个middleware,叫log来简化状况,这里的类应该是一个类全路径,我这里就简单的写一下,要否则太长了。

    $middleware_arr = ['log'];

#最终要执行的代码先封装成一个闭包,要否则没有办法传递到内层,若是用函数名传递函数的话,是没有办法传递参数的。

    $default = function() use($request){

        return $kernel->handle($request);

    }

    $callback = array_reduce($middleware_arr,function($stack,$pipe) {

        return function() use($stack,$pipe){

          return $pipe::handle($stack);

        };

    },$default);

     

     

# 这里 callback最终是 这样一个函数:

    function() use($default,$log){

          return $log::handle($default);

        };

         

#因此每个中间件都须要有一个方法handle方法,方法中要对传输的函数进行运行,相似以下,这里我类名就不大写了

    class log implements Milldeware {

        public static function handle(Closure $func)

        {

            $func();

        }

    }

     

#这里不难看出能够加入中间件自身逻辑以下:

 class log implements Milldeware {

        public static function handle(Closure $func)

        {

            #这里能够运行逻辑块before()

            $func();

            #这里能够运行逻辑块after()

        }

    }

  

这样在执行callback函数的时候,执行顺序以下:

先运行log::haddle()方法,

执行了log::before()方法

运行default方法,执行$kernel->handle($request)

运行log::after()方法

而后模拟多个的状况以下:

    $middleware_arr = ['csrf','log'];

#最终要执行的代码先封装成一个闭包,要否则没有办法传递到内层,若是用函数名传递函数的话,是没有办法传递参数的。

    $default = function() use($request){

        return $kernel->handle($request);

    }

    $callback = array_reduce($middleware_arr,function($stack,$pipe) {

        return function() use($stack,$pipe){

          return $pipe::handle($stack);

        };

    },$default);

     

     

# 这里 callback最终是 执行这样:

    $log::handle(function() use($default,$csrf){

                    return $csrf::handle($default);

                });

  

执行顺序以下:

1.先运行log::haddle(包含csrf::handle闭包函数)方法,

2.执行了log::before()方法

3.运行闭包也就是运行了$csrf::handle($default)

4.执行了csrf::before()方法

5.运行default方法,执行$kernel->handle($request)

6.执行了csrf::after()方法

7.运行log::after()方法

注意这里还有一个问题就是中间件产生的结果,并无进行传递,能够经过修改共有资源的方式来达到相同的目的,并不是须要真的传值到下一个中间件。

到此这篇文件就结束了,其实其中不少关节都是我写这篇文章的时候才想明白的。尤为是对闭包函数的运用和理解更深了,闭包函数能够延迟利用资源,好比当前不适合执行的语句,又要传递到后面,利用闭包能够封装起来传递出去,这是传统函数作不到的。

以上就是laravel中间件的建立思路分析的详细内容

相关文章
相关标签/搜索