网上有很多解析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函数的时候,执行顺序如下:

  1. 先运行log::haddle()方法,
  2. 执行了log::before()方法
  3. 运行default方法,执行$kernel->handle($request)
  4. 运行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的中间件创建思路的更多相关文章

  1. laravel中间件的创建思路分析

    网上有很多解析laravel中间件的实现原理,但是不知道有没有读者在读的时候不明白,作者是怎么想到要用array_reduce函数的? 本文从自己的角度出发,模拟了如果我是作者,我是怎么实现这个中间件 ...

  2. 深入解析Laravel的中间件

    Laravel 中间件是什么? 简而言之,中间件在 laravel 中的作用就是过滤 HTTP 请求,根据不同的请求来执行不同的逻辑操作. 我们可以通过中间件实现以下功能: 指定某些路由 设置 HTT ...

  3. Laravel 使用中间件进行权限控制

    Laravel 使用中间件进行权限控制 飞凡的陀螺 关注 2018.01.24 17:45 字数 264 阅读 1138评论 0喜欢 1 先看 文档Laravel 中间件提供了一种方便的机制来过滤进入 ...

  4. Laravel之中间件

    一.中间件的作用 HTTP 中间件提供了一个便利的机制来过滤进入应用的 HTTP 请求.例如,Laravel 包含了一个中间件来验证用户是否经过授权,如果用户没有经过授权,中间件会将用户重定向到登录页 ...

  5. laravel 加中间件的方法 防止直接打开后台

    路由 routes.php Route::group(['middleware' => ['web','admin.login.login']], function () { //后台首页路由 ...

  6. laravel数据库的创建和迁移

    数据库建立及迁移 Laravel 5 把数据库配置的地方改到了 `learnlaravel5/.env`,打开这个文件,编辑下面四项,修改为正确的信息: ? 1 2 3 4 5 6 7 DB_HOST ...

  7. .net 平台下, Socket通讯协议中间件设计思路(附源码)

    .net 平台下,实现通讯处理有很多方法(见下表),各有利弊: 序号 实现方式 特点 1 WCF 优点:封装好,方便.缺点:难学,不跨平台 2 RocketMQ,SuperSocket等中间件 优点: ...

  8. Laravel 5 中间件、路由群组、子域名路由、 权限控制的基本使用方法

    创建控制器: php artisan make:controller Admin/IndexController 创建Middleware: php artisan make:middleware T ...

  9. laravel使用Schema创建数据表

    1.简介 迁移就像数据库的版本控制,允许团队简单轻松的编辑并共享应用的数据库表结构,迁移通常和Laravel的schema构建器结对从而可以很容易地构建应用的数据库表结构.如果你曾经告知小组成员需要手 ...

随机推荐

  1. Django+Celery框架自动化定时任务开发

    本章介绍使用DjCelery即Django+Celery框架开发定时任务功能,在Autotestplat平台上实现单一接口自动化测试脚本.业务场景接口自动化测试脚本.App自动化测试脚本.Web自动化 ...

  2. 机器学习 —— 数据预处理

    对于学习机器学习算法来说,肯定会涉及到数据的处理,因此一开始,对数据的预处理进行学习 对于数据的预处理,大概有如下几步: 步骤1 -- 导入所需库 导入处理数据所需要的python库,有如下两个库是非 ...

  3. RocketMQ 零拷贝

    一.零拷贝原理:Consumer 消费消息过程,使用了零拷贝,零拷贝包含以下两种方式: 1.使用 mmap + write 方式  (RocketMQ选择的方式:因为有小块数据传输的需求,效果会比 s ...

  4. Java入门教程三(流程控制)

    概述 程序有 3 种结构:顺序结构.选择结构和循环结构.分别为:if elseswitch case,while与do while,for, foreach,return,break ,continu ...

  5. APScheduler使用总结

    安装 pip install apscheduler APScheduler组件 1.triggers(触发器) 触发器中包含调度逻辑,每个作业都由自己的触发器来决定下次运行时间.除了他们自己初始配置 ...

  6. JDBC大数据的采取

    ## JDBC的大类型数据的存取 ## # 基本概念: |-- 大文本类型数据和大二进制数据: 主要思想用于将大型的二进制数据(字节) 或是大型的文本数据(字符)从磁盘文件中读取 到数据库中,或是从数 ...

  7. 从头解决PKIX path building failed

    从头解决PKIX path building failed的问题 本篇涉及到PKIX path building failed的原因和解决办法(包括暂时解决和长效解决的方法),也包括HTTP和HTTP ...

  8. 「前端」rem 缩放方案 flexible-js 兼容 375px 方案的思路

    本文来自尚妆前端团队南洋 发表于尚妆github博客,欢迎订阅. 移动端H5页面rem缩放方案flexible.js兼容375px方案的思路 参考: 移动端高清.多屏适配方案 viewport-and ...

  9. 前端的事件冒泡(例如点击一次onclick事件执行两次)解决办法

    问题概要: 当我运用antd 中 radio组件的时候发现radio组件是有bug的 就是你不能给他赋予id 和 value,同时也绑定不上onclick等事件.举个例子: 可以看到 你就算赋予了id ...

  10. Swfit 属性与汇编分析inout本质

    今天将讲述Swift属性以及剖析inout的本质, 如有兴趣可点击关注,以后会定期更新更有料的博客!!! 一.属性 Swift中跟实例相关的属性可以分为2大类 存储属性(Stored property ...