基于laravel5.2进行中间件源码的解析
发布时间 - 2018-07-31 00:00:00 点击率:次在laravel5.2
中,http的主要作用就是过滤http请求(php aritsan是没有中间件机制的),同时也让系统的层次(http过滤层)更明确,使用起来也很优雅。但实现中间件的代码却很复杂,下面就来具分析下有关中间件的源码的内容。
中间件源码
中间件本身分为两种,一种是所有http的,另一种则是针对route的。一个有中间件的请求周期是:Request得先经过Http中间件,才能进行Router,再经过Requset所对应Route的Route中间件, 最后才会进入相应的Controller代码。laravel把请求分为了两种:http和console。不同的请求方式用它自己的Kernel来驱动Application。Http请求则是通过\Illuminate\Foundation\Http\Kernel类来驱动,它定义了所有的中间件,其父类\Illuminate\Foundation\Http\Kernel::handle就是对请求进行处理的入口了
Http中间件
跟踪入口handle()方法,很容易发现该函数(\Illuminate\Foundation\Http\Kernel::sendRequestThroughRouter):
protected function sendRequestThroughRouter($request)
{
$this->app->instance('request', $request);
Facade::clearResolvedInstance('request');
$this->bootstrap();
return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
}该函数会把Requset分发到Router(通过方法名就知道了), 主要的逻辑则是通过\Illuminate\Routing\Pipeline完成的, 作用就是让Requset通过Http中间件的检测,然后再到达Router。这里的代码看起来很优雅,但不是很好理解。所以,了解Pipeline的运行机制就会明白中间件的使用。
Pipeline的运行实现
Pipleline基类是\Illuminate\Pipeline\Pipeline,它的执行在then方法:
public function then(Closure $destination)
{
$firstSlice = $this->getInitialSlice($destination);
$pipes = array_reverse($this->pipes);
return call_user_func(
array_reduce($pipes, $this->getSlice(), $firstSlice), $this->passable
);
}了解这段代码执行的意图,必须要知道array_reduce()做了什么。 为了清楚array_reduce怎么运行的,先把array_reduce重写一次:
//将数组中的元素,依次执行$func函数,且上一次的$func的返回值作为下一次调用$func的第一个参数输入
function array_reduce_back($arr, callable $func, $firstResult = null)
{
$result = $firstResult;
foreach ($arr as $v) {
$result = $func($result, $v);
}
return $result;
}所以,源代码中的$func是getSlice(),它返回的是一个回调函数:function($passable) use ($stack, $pipe){...}($stack和$pipe被输入的具体值代替),也就是说作为上一次返回结果输入到下一次$func的第一个参数是上述的回调函数,如此循环,当数组遍历完成,array_reduce就返回的是一个回调函数,现在关键就是了解这个回调函数是什么样子,又如何执行?为方便讨论,可分析下面的代码:
call_user_func(
array_reduce([1, 2, 3], $this->getSlice(), $firstSlice), $this->passable
);
执行说明:
1.$result_0是初始化的值 ,为$firstSlice ,即是\Illuminate\Pipeline\Pipeline::getInitialSlice的返回回调
2.每遍历一个元素,都会执行\Illuminate\Pipeline\Pipeline::getSlice的回调,同时也会返回一个回调
3.$result中的具体执行代码都在getSlice()中
4.最后的array_reduce返回结果是$result_3,是一个有多层闭包的回调函数
5.执行的是call_user_func($result_3, $this->passable),即执行function($this->passable) use ($result_2, 3){...}
至此已经清楚了then()是如何运行的了,要继续下去,则需再搞定回调函数到底怎么执行的.现在再跟着sendRequestThroughRouter中的Pipeline走,看它是如何执行的。
// 把具体的参数带进来
return (new Pipeline($this->app))
->send($request)
->through(['\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode'])
->then($this->dispatchToRouter());用上面的所分析的Pipeline执行过程,很快就会分析出最后执行的是
function($requset) use (\Illuminate\Foundation\Http\Kernel::dispatchToRouter(), '\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode') {
if ($pipe instanceof Closure) {
return call_user_func($pipe, $passable, $stack);
}
// $name和$parameters很容易得到
// $name = '\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode';
// $parameters = [];
list($name, $parameters) = $this->parsePipeString($pipe);
// 执行的就是\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::handle($request, \Illuminate\Foundation\Http\Kernel::dispatchToRouter())
return call_user_func_array([$this->container->make($name), $this->method],
array_merge([$passable, $stack], $parameters));
}逻辑处理已经到了\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::handle,其代码是:
public function handle($request, Closure $next)
{
if ($this->app->isDownForMaintenance()) {
throw new HttpException(503);
}
return $next($request);
}这里,它处理了这个中间件所需要过滤的条件,同时执行了$next($request),即\Illuminate\Foundation\Http\Kernel::dispatchToRouter(), 这样,就把Request转到了Router中,也就完成了Http中间件的所有处理工作,而$next($request)是每个中间件都不可少的操作,因为在回调中嵌套了回调,就是靠中间件把Request传递到下一个回调中,也就会解析到下一个中间件,直到最后一个。紧跟上面的已分析的Pipeline执行过程,讲其补充完整:
6.执行$result_3中的回调,getSlice实例化中间件,执行其handle,在中间件处理中执行回调
7.回调中还嵌套回调的,每个中间件中都需有执行回调的代码$next($request) ,才能保证回调中的回调会执行,执行的顺序就是3::handel,2::handel,1::handel,$first
8.最里面一层,一定是传递给then()的参数,then执行的就是最后一步
9.执行的顺序是由数组中的最后一个,向前,到then()的参数,为了使其执行顺序是数组中的第一个到最后一个,再到then()中的参数,then()方法中就做了一个反转array_reverse
Pipeline小结
现在,Pipeline的所有执行流程就都分析完了。实现代码真的很绕,但理解之后编写自定义的中间件应该就很容易了。现在再把Pipeline的使用翻译成汉语,应该是这样的
// 使用管道,发送$request,使之通过middleware ,再到$func (new Pipeline($this->app))->send($request)->through($this->middleware)->then($func);
这样的代码不管是从语义上,还是使用上都很优雅,高!确实是高!再回到源码,Requset的流程就通过dispatchToRouter进入到了Router
Route中间件
在Router中,\Illuminate\Routing\Router::dispatch就承接了来自Http中间件的Requset, Router把Request分发到了具体的Route,再进行处理,主要代码如下:
public function dispatchToRoute(Request $request)
{
// 找到具体的路由对象,过程略
$route = $this->findRoute($request);
$request->setRouteResolver(function () use ($route) {
return $route;
});
// 执行Request匹配到Route的事件,具体的代码在这里:\Illuminate\Foundation\Providers\FoundationServiceProvider::configureFormRequests
$this->events->fire(new Events\RouteMatched($route, $request));
// 这里就运行路由中间件了
$response = $this->runRouteWithinStack($route, $request);
return $this->prepareResponse($request, $response);
}
protected function runRouteWithinStack(Route $route, Request $request)
{
// 获取该路由上的中间件
// 简单就点可这样写:
// $middleware = App::shouldSkipMiddleware() ? [] : $this->gatherRouteMiddlewares($route);
$shouldSkipMiddleware = $this->container->bound('middleware.disable') &&
$this->container->make('middleware.disable') === true;
$middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddlewares($route);
// 了解Pipeline后,这里就好理解了,应该是通过管道,发送$request,经过$middleware,再到then中的回调
return (new Pipeline($this->container))
->send($request)
->through($middleware)
->then(function ($request) use ($route) {
return $this->prepareResponse(
$request,
$route->run($request)
);
});
}如何获取Route中间件的,就可以跟gatherRouteMiddlewares,这个代码并不难,很好跟。接下来,Request就到到达至于Controller, Request是如何到达Controller的代码就不难了,这里就不说了
Controller后执行中间件
成功获取Response后,在public/index.php58行执行了$kernel->terminate($request, $response);, 也就是在主要逻辑处理完成之后,再执行此代码,它实际上调用是的\Illuminate\Foundation\Http\Kernel::terminate, 跟进去就很容易发现,它处理了这此请求所涉及到的中间件,并执行了各自的terminate方法,到这里,中间件的另一个功能就展现出来了,就是主要逻辑完成之后的收尾工作.到这里为止,中间件就完成了它的使命(一个请求也就完成了)
如何使用中间件
在官方文档上讲解的很清楚注册中间
中间件小结
至此,中间件的实现逻辑与使用就清晰了.从执行的顺序来分,一个在Controller之前,一个在Controller之后,所以它一个很重要的作用就是可以让Controller专注于自己的主要逻辑的职责更明确. 奇怪的是,但前后两种中间件的执行方式却不一样, \Illuminate\Foundation\Http\Kernel::terminate,中间件的结束却没有使用Pipeline, 而是直接foreach.相同的工作却用两种代码来实现.现在看来,中间件本身并不复杂,但它带给了我两个启发,1.层次明确 2,Pipeline所带来的优雅.
相关推荐:
laravel5.4中自定义包开发的实例
Laravel 5.1框架中如何创建自定义Artisan控制台命令
laravel框架的启动过程分析
# php
# laravel
# 中间件
# foreach
# 父类
# 回调函数
# 循环
# public
# 闭包
# console
# function
# this
# http
# 回调
# 的是
# 两种
# 很容易
# 就会
# 第一个
# 也就
# 则是
# 自己的
# 自定义
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
网站优化排名时,需要考虑哪些问题呢?
Android自定义listview布局实现上拉加载下拉刷新功能
Linux后台任务运行方法_nohup与&使用技巧【技巧】
Laravel如何使用Guzzle调用外部接口_Laravel发起HTTP请求与JSON数据解析【详解】
在Oracle关闭情况下如何修改spfile的参数
Laravel怎么使用Session存储数据_Laravel会话管理与自定义驱动配置【详解】
北京网页设计制作网站有哪些,继续教育自动播放怎么设置?
网站制作大概多少钱一个,做一个平台网站大概多少钱?
关于BootStrap modal 在IOS9中不能弹出的解决方法(IOS 9 bootstrap modal ios 9 noticework)
Win11怎么更改系统语言为中文_Windows11安装语言包并设为显示语言
Laravel如何使用Contracts(契约)进行编程_Laravel契约接口与依赖反转
谷歌浏览器下载文件时中断怎么办 Google Chrome下载管理修复
php读取心率传感器数据怎么弄_php获取max30100的心率值【指南】
如何用wdcp快速搭建高效网站?
javascript读取文本节点方法小结
Laravel如何实现事件和监听器?(Event & Listener实战)
Laravel如何安装Breeze扩展包_Laravel用户注册登录功能快速实现【流程】
javascript基于原型链的继承及call和apply函数用法分析
什么是JavaScript解构赋值_解构赋值有哪些实用技巧
Laravel怎么上传文件_Laravel图片上传及存储配置
如何获取上海专业网站定制建站电话?
家族网站制作贴纸教程视频,用豆子做粘帖画怎么制作?
Python正则表达式进阶教程_复杂匹配与分组替换解析
JS中使用new Date(str)创建时间对象不兼容firefox和ie的解决方法(两种)
安克发布新款氮化镓充电宝:体积缩小 30%,支持 200W 输出
矢量图网站制作软件,用千图网的一张矢量图做公司app首页,该网站并未说明版权等问题,这样做算不算侵权?应该如何解决?
Laravel怎么使用Intervention Image库处理图片上传和缩放
Laravel如何实现全文搜索功能?(Scout和Algolia示例)
C++时间戳转换成日期时间的步骤和示例代码
韩国服务器如何优化跨境访问实现高效连接?
如何在阿里云ECS服务器部署织梦CMS网站?
教学论文网站制作软件有哪些,写论文用什么软件
?
如何用PHP快速搭建高效网站?分步指南
百度输入法全感官ai怎么关 百度输入法全感官皮肤关闭
Laravel Eloquent:优雅地将关联模型字段扁平化到主模型中
Laravel怎么使用Collection集合方法_Laravel数组操作高级函数pluck与map【手册】
湖南网站制作公司,湖南上善若水科技有限公司做什么的?
微信小程序 HTTPS报错整理常见问题及解决方案
标准网站视频模板制作软件,现在有哪个网站的视频编辑素材最齐全的,背景音乐、音效等?
使用豆包 AI 辅助进行简单网页 HTML 结构设计
使用PHP下载CSS文件中的所有图片【几行代码即可实现】
如何用JavaScript实现文本编辑器_光标和选区怎么处理
网页制作模板网站推荐,网页设计海报之类的素材哪里好?
Laravel如何使用Facades(门面)及其工作原理_Laravel门面模式与底层机制
如何在IIS中新建站点并解决端口绑定冲突?
如何彻底删除建站之星生成的Banner?
如何在云服务器上快速搭建个人网站?
Laravel如何实现URL美化Slug功能_Laravel使用eloquent-sluggable生成别名【方法】
百度输入法ai面板怎么关 百度输入法ai面板隐藏技巧
谷歌Google入口永久地址_Google搜索引擎官网首页永久入口

