浅谈laravel中间件的创建思路

 更新时间:2021年05月29日 14:23:37   作者:八重樱  
网上有很多解析laravel中间件的实现原理,但是不知道有没有读者在读的时候不明白,作者是怎么想到要用array_reduce函数的?本文从自己的角度出发,模拟了如果我是作者,我是怎么实现这个中间件功能,又是怎么找到并使用对应的函数。本文将介绍Laravel中间件得创建思路。

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

那么我们简化一下软件执行过程,现在有一个核心类kernel,下面是它的laravel代码

#捕获请求
$request = Illuminate\Http\Request::capture()
#处理请求
$response = $kernel->handle($request);

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

那么如果想在执行这个$kernel->handle()方法之前或者之后,增加一段逻辑一般会怎么写呢。大概如下:

$request = Illuminate\Http\Request::capture()
function midware(){
    before()#在之前执行的语句集合
    #####   
    $response = $kernel->handle($request);
    #####
    after()#在之后执行的语句集合
 
}

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

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

#配置项中有一项配置中间件:
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中间件的创建思路的详细内容,更多关于laravel中间件的创建思路的资料请关注脚本之家其它相关文章!

相关文章

  • php实现的Cookies操作类实例

    php实现的Cookies操作类实例

    这篇文章主要介绍了php实现的Cookies操作类及其用法实例,包括了常见了保存、读取、更新及清除cookie等操作,在需要进行cookie操作时非常具有实用价值,需要的朋友可以参考下
    2014-09-09
  • PHP实现适用于文件内容操作的分页类

    PHP实现适用于文件内容操作的分页类

    这篇文章主要为大家详细介绍了PHP实现适用于文件内容操作的分页类,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-06-06
  • 详解PHP中array_rand函数的使用方法

    详解PHP中array_rand函数的使用方法

    大家都知道array_rand是数组随机函数了,今天看到一个站长简单的介绍array_rand性能了,于是把许久没写的php再来简单的看看,我们一起来看看array_rand函数用法吧。有需要的朋友们可以参考学习。
    2016-09-09
  • PHP类继承 extends使用介绍

    PHP类继承 extends使用介绍

    我们在文章中为大家详细介绍了有关PHP类继承 extends的实现方法,希望对于新手来说能够提升他们在PHP语言编程中的能力
    2014-01-01
  • php7性能提升的原因详解

    php7性能提升的原因详解

    在本篇文章里小编给大家分享是的关于php7性能提升的原因以及相关知识点,有需要的朋友们参考下。
    2019-10-10
  • Optimizer与Debugger兼容性问题的解决方法

    Optimizer与Debugger兼容性问题的解决方法

    网上许多声音说Optimizer与Debugger有冲突,不能同时开。其实是可以的,他们两个都是Zend扩展插件,两个单独安装都可以使用,但是要同时安装的话必须使用一个管理器
    2008-12-12
  • PHP+Ajax实现的无刷新分页功能详解【附demo源码下载】

    PHP+Ajax实现的无刷新分页功能详解【附demo源码下载】

    这篇文章主要介绍了PHP+Ajax实现的无刷新分页功能,结合具体实例形式分析了php+ajax通过pdo操作类读取数据库并分页显示的相关实现技巧,并附带demo源码供读者下载参考,需要的朋友可以参考下
    2017-07-07
  • php获取服务器端mac和客户端mac的地址支持WIN/LINUX

    php获取服务器端mac和客户端mac的地址支持WIN/LINUX

    这篇文章主要介绍了php获取服务器端mac和客户端mac地址的方法,需要的朋友可以参考下
    2014-05-05
  • php中获取主机名、协议及IP地址的方法

    php中获取主机名、协议及IP地址的方法

    这篇文章主要介绍了php中获取主机名、协议及IP地址的方法,其中涉及gethostbyaddr、gethostbynamel、getprotobyname、getprotobynumber等函数及$_SERVER变量的应用,需要的朋友可以参考下
    2014-11-11
  • php获取twitter最新消息的方法

    php获取twitter最新消息的方法

    这篇文章主要介绍了php获取twitter最新消息的方法,涉及php操作curl及正则替换的技巧,非常具有实用价值,需要的朋友可以参考下
    2015-04-04

最新评论