首页 php框架 Laravel laravel中间件的创建思路分析

laravel中间件的创建思路分析

Mar 29, 2020 am 09:07 AM
laravel

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

推荐:laravel教程

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

什么是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 = [&#39;log&#39;];
#最终要执行的代码先封装成一个闭包,要不然没有办法传递到内层,如果用函数名传递函数的话,是没有办法传递参数的。
    $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 = [&#39;csrf&#39;,&#39;log&#39;];
#最终要执行的代码先封装成一个闭包,要不然没有办法传递到内层,如果用函数名传递函数的话,是没有办法传递参数的。
    $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中间件的创建思路分析的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

在Laravel中如何获取邮件发送失败时的退信代码? 在Laravel中如何获取邮件发送失败时的退信代码? Apr 01, 2025 pm 02:45 PM

Laravel邮件发送失败时的退信代码获取方法在使用Laravel开发应用时,经常会遇到需要发送验证码的情况。而在实�...

在 Laravel 中,如何处理邮件发送验证码失败的情况? 在 Laravel 中,如何处理邮件发送验证码失败的情况? Mar 31, 2025 pm 11:48 PM

Laravel邮件发送验证码失败时的处理方法在使用Laravel...

在dcat admin中如何实现点击添加数据的自定义表格功能? 在dcat admin中如何实现点击添加数据的自定义表格功能? Apr 01, 2025 am 07:09 AM

在dcatadmin(laravel-admin)中如何实现自定义点击添加数据的表格功能在使用dcat...

Laravel Redis连接共享:为何select方法会影响其他连接? Laravel Redis连接共享:为何select方法会影响其他连接? Apr 01, 2025 am 07:45 AM

Laravel框架中Redis连接的共享与select方法的影响在使用Laravel框架和Redis时,开发者可能会遇到一个问题:通过配置...

Laravel多租户扩展stancl/tenancy:如何自定义租户数据库连接的主机地址? Laravel多租户扩展stancl/tenancy:如何自定义租户数据库连接的主机地址? Apr 01, 2025 am 09:09 AM

在Laravel多租户扩展包stancl/tenancy中自定义租户数据库连接使用Laravel多租户扩展包stancl/tenancy构建多租户应用时,...

Bangla 部分模型检索中的 Laravel Eloquent ORM) Bangla 部分模型检索中的 Laravel Eloquent ORM) Apr 08, 2025 pm 02:06 PM

LaravelEloquent模型检索:轻松获取数据库数据EloquentORM提供了简洁易懂的方式来操作数据库。本文将详细介绍各种Eloquent模型检索技巧,助您高效地从数据库中获取数据。1.获取所有记录使用all()方法可以获取数据库表中的所有记录:useApp\Models\Post;$posts=Post::all();这将返回一个集合(Collection)。您可以使用foreach循环或其他集合方法访问数据:foreach($postsas$post){echo$post->

在Laravel6项目中如何有效检查Redis连接的有效性? 在Laravel6项目中如何有效检查Redis连接的有效性? Apr 01, 2025 pm 02:00 PM

在Laravel6项目中如何检查Redis连接的有效性是一个常见的问题,特别是在项目依赖于Redis进行业务处理时。以下是...

Laravel数据库迁移遇到类重复定义:如何解决迁移文件重复生成及类名冲突? Laravel数据库迁移遇到类重复定义:如何解决迁移文件重复生成及类名冲突? Apr 01, 2025 pm 12:21 PM

Laravel数据库迁移过程中出现类重复定义问题在使用Laravel框架进行数据库迁移时,开发者可能会遇到“类已使用�...

See all articles