laravel中间件的创建思路分析
网上有很多解析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中间件的创建思路分析的更多相关文章
- laravel的中间件创建思路
网上有很多解析laravel中间件的实现原理,但是不知道有没有读者在读的时候不明白,作者是怎么想到要用array_reduce函数的? 本文从自己的角度出发,模拟了如果我是作者,我是怎么实现这个中间件 ...
- laravel中间件源码分析
laravel中间件源码分析 在laravel5.2中,HTTP 中间件为过滤访问你的应用的 HTTP 请求提供了一个方便的机制.在处理逻辑之前,会通过中间件,且只有通过了中间件才会继续执行逻辑代码. ...
- (学习笔记)laravel 中间件
(学习笔记)laravel 中间件 laravel的请求在进入逻辑处理之前会通过http中间件进行处理. 也就是说http请求的逻辑是这样的: 建立中间件 首先,通过Artisan命令建立一个中间件. ...
- 2018-8-16JWTtoken用户登录认证思路分析9502751
2018-8-16JWTtoken用户登录认证思路分析9502751 JWT token在商城中的实现 class UserView(CreateAPIView): serializer_class ...
- laravel中间件的使用
简介HTTP 中间件提供了为过滤进入应用的 HTTP 请求提供了一套便利的机制.例如,Laravel 内置了一个中间件来验证用户是否经过授权,如果用户没有经过授权,中间件会将用户重定向到登录页面,否则 ...
- laravel中间件使用
1.在app/Http/Kernel.php文件中配置中间件文件,例如: protected $routeMiddleware = [ 'auth' => \Illuminate\Auth\Mi ...
- 八大排序算法详解(动图演示 思路分析 实例代码java 复杂度分析 适用场景)
一.分类 1.内部排序和外部排序 内部排序:待排序记录存放在计算机随机存储器中(说简单点,就是内存)进行的排序过程. 外部排序:待排序记录的数量很大,以致于内存不能一次容纳全部记录,所以在排序过程中需 ...
- 八大排序算法——堆排序(动图演示 思路分析 实例代码java 复杂度分析)
一.动图演示 二.思路分析 先来了解下堆的相关概念:堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆:或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆.如 ...
- 八大排序算法——希尔(shell)排序(动图演示 思路分析 实例代码java 复杂度分析)
一.动图演示 二.思路分析 希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序:随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止. 简单插 ...
随机推荐
- loadrunner通过web的post请求方法测接口
loadrunner通过web的post请求方法测接口 loginapi() 模拟APP发送请求给Cloud, Action() "Name=input","Value= ...
- Bugku的一道注入
继续补sqli的题 这道题与之前的题的区别是在第二部分中加了一道waf,所以需要特殊的手段来进行注入. 题目来源:http://123.206.87.240:9004/1ndex.php?id=1 第 ...
- GNS3(1)——OSPF多区域配置
GNS3(1)——OSPF多区域配置 RIP适用于中小网络,比较简单.没有系统内外.系统分区,边界等概念,用到不是分类的路由. OSPF适用于较大规模网络.它把自治系统分成若干个区域,通过系列内外路由 ...
- Nginx 配置GeoIP2 禁止访问,并允许添加白名单过滤访问设置
配置环境:Centos 7.6 + Tengine 2.3.2 GeoIP2 下载地址:https://dev.maxmind.com/geoip/geoip2/geolite2/ 1. Nginx ...
- nginx 命令行参数 启动 重启 重载 停止
今天和大家分享关于 nginx 的一些参数使用 首先,你应该安装了nginx CentOS 安装 nginx 这是很早之前的一篇博客,可以参考. 之前,我们如何去操作 nginx ##简单粗暴法 pk ...
- [红日安全]Web安全Day8 - XXE实战攻防
本文由红日安全成员: ruanruan 编写,如有不当,还望斧正. 大家好,我们是红日安全-Web安全攻防小组.此项目是关于Web安全的系列文章分享,还包含一个HTB靶场供大家练习,我们给这个项目起了 ...
- Windows下利用virtualenvwrapper指定python版本创建虚拟环境
默认已安装virtualenvwrapper 一.添加环境变量(可选) 在系统环境变量中添加 WORKON_HOME ,用来指定新建的虚拟环境的存储位置,如过未添加,默认位置为 %USERPROFIL ...
- 用 HTML5 造个有诚意的 23D 招聘稿
前言 招聘对于一个公司来说是相当重要的一个环节,首先它影响着公司未来发展的趋势,其次它为公司注入新鲜血液,使公司更具有活力.当然在工业互联网,物联网大背景下诞生的 HT 也是需要注入新鲜的血液来进一步 ...
- 高可用Keepalived+LVS搭建流程
本流程搭建1个master,1个backup节点的Keepalived,使用lvs轮询2个节点的服务. 一.使用版本 CentOS 7.7 Keepalived 1.3.5 ipvsadm 1.27( ...
- 第16个算法 - leetcode-二叉树的层次遍历
二叉树的层次遍历 参考:https://www.cnblogs.com/patatoforsyj/p/9496127.html 给定一个二叉树,返回其按层次遍历的节点值. (即逐层地,从左到右访问所有 ...