laravel5.5源码笔记(六、中间件)
laravel中的中间件作为一个请求与响应的过滤器,主要分为两个功能。
1、在请求到达控制器层之前进行拦截与过滤,只有通过验证的请求才能到达controller层
2、或者是在controller中运算完的数据或页面响应返回前进行过滤,通过验证的响应才能返回给客户端
中间件一般通过artisan命令创建
php artisan make:middleware 中间件名称
命令行创建的中间件会保存在/app/Http/Middleware文件夹里
这些中间件需要手动添加在/app/Http/Kernel.php文件的配置中,这个文件继承了kernel核心,在应用入口初始化kernel核心时,vendor\laravel\framework\src\Illuminate\Foundation\Http\Kernel.php这个kernel文件中的配置便覆盖了kernel核心中的相关配置,其中当然便包括了middleware中间件。我们可以在kernel类的构造方法第一行添加打印语句,分别打印$this->middleware,$this->middlewareGroups然后我们就会看见之前写在/app/Http/Kernel.php文件中的中间件了。

不过不止这里一处,在路由加载的时候,也会加载一次系统默认的web数组中的中间件,在返回响应对象的时候进行过滤,这个稍后再看。
接下来看一下中间件是在哪里开始过滤的,在第一篇关于入口文件的博文中,我们了解到laravel是从kernel核心中的handle方法开始的。
public function handle($request)
{
try {
//启用http方法覆盖参数
$request->enableHttpMethodParameterOverride();
//通过路由发送请求
$response = $this->sendRequestThroughRouter($request);
} catch (Exception $e) {
$this->reportException($e); $response = $this->renderException($request, $e);
} catch (Throwable $e) {
$this->reportException($e = new FatalThrowableError($e)); $response = $this->renderException($request, $e);
} $this->app['events']->dispatch(
new Events\RequestHandled($request, $response)
); return $response;
} protected function sendRequestThroughRouter($request)
{
//将请求存入容器
$this->app->instance('request', $request);
//清除facade门面
Facade::clearResolvedInstance('request');
//初始化引导
$this->bootstrap();
//让请求进入中间件
return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
}
kernel核心的sendRequestThroughRouter方法中最后的return 语句,便开始使用中间件来对系统接到的请求开始进行过滤了。
上一篇博文中,我们提到了装饰模式与laravel在此基础上变种来的管道模式,便是在这里开始运用。
从Pipeline对象一路跳转,发现这个对象的基类为laravel\vendor\laravel\framework\src\Illuminate\Pipeline\Pipeline.php。管道对象的构造方法把laravel系统容器传入了其中。这里不再赘述管道模式的原理,直接来看关键方法then。
public function then(Closure $destination)
{
//array_reduce — 用回调函数迭代地将数组简化为单一的值
$pipeline = array_reduce(
//array_reverse将数组反向排序,$this->carry()返回一个闭包函数用来迭代
array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination)
); return $pipeline($this->passable);
} protected function carry()
{
return function ($stack, $pipe) {
return function ($passable) use ($stack, $pipe) {
if (is_callable($pipe)) {
// If the pipe is an instance of a Closure, we will just call it directly but
// otherwise we'll resolve the pipes out of the container and call it with
// the appropriate method and arguments, returning the results back out.
return $pipe($passable, $stack);
} elseif (! is_object($pipe)) {
//解析字符串
list($name, $parameters) = $this->parsePipeString($pipe); // If the pipe is a string we will parse the string and resolve the class out
// of the dependency injection container. We can then build a callable and
// execute the pipe function giving in the parameters that are required.
//从容器中取出相应的对象
$pipe = $this->getContainer()->make($name);
$parameters = array_merge([$passable, $stack], $parameters);
} else {
// If the pipe is already an object we'll just make a callable and pass it to
// the pipe as-is. There is no need to do any extra parsing and formatting
// since the object we're given was already a fully instantiated object.
$parameters = [$passable, $stack];
}
dd($parameters);
//判断中间件对象中是否存在handle方法,并将刚刚的$request对象与$stack闭包作为参数,传入中间件handle方法
return method_exists($pipe, $this->method)
? $pipe->{$this->method}(...$parameters)
: $pipe(...$parameters);
};
};
}
可以看到,管道类中的其他几个方法都是很简单的赋值方法。而then方法中,写着的也是我们上一次理解管道模式中用到的array_reduce函数,看到这个就亲切多了。array_reverse($this->pipes)也就是将中间件数组逆向一下,因为管道模式的闭包运行时是像洋葱一样从外到内一层一层。而$this->prepareDestination($destination)这个方法则是之前then方法传入的,通过中间件后,需要执行的业务代码,也就是会开始调用控制器了。
关键代码在$this->carry()中这个方法只看最外层的话,就是返回了一个闭包函数,相当于直接把这个闭包写在array_reduce中。里面那个闭包函数则是我们比较熟悉的管道闭包写法了,use的两个参数分别代表本次闭包的执行结果与中间件数组中的一个参数。$passable变量则代表了$request对象。
这个闭包中,用了几个if判断,来确定$pipe的值类型,也就是我们的中间件数组的值中的类型,如果是闭包则直接运行,如果不是对象则从容器中加载出对象。如果剩下的都不是,则代表类型为对象,直接构建参数,开始调用。还记得我们的自定义中间件代码都要写在handle方法中吗,这里的$passable 和 $stack便是运行时传入handle的变量$request 与 Closure $next了。而这里的next方法又是什么呢?
让我们打开路由中的管道文件vendor\laravel\framework\src\Illuminate\Routing\Pipeline.php看一看
protected function carry()
{
return function ($stack, $pipe) {
return function ($passable) use ($stack, $pipe) {
try {
//通过这个函数启动刚刚的管道闭包
$slice = parent::carry();
//$stack, $pipe依然是闭包结果与中间件数组中的值
$callable = $slice($stack, $pipe);
//$passable还是request对象
return $callable($passable);
} catch (Exception $e) {
return $this->handleException($passable, $e);
} catch (Throwable $e) {
return $this->handleException($passable, new FatalThrowableError($e));
}
};
};
}
当这个管道闭包执行完毕之后,中间件暂时就结束了。便开始执行之前kernel核心传入的$this->dispatchToRouter()方法了。
现在来看一下路由通过中间件之后,是如何到达控制器的。dispatchToRouter方法经过一系列跳转之后,到达了Router类的dispatchToRoute方法。其中findRoute方法获取到了Routes对象集合(这个对象中包含了所有的路由对象,在之前介绍路由的章节时介绍过)并通过请求对象从中获取了当前路由(这个查找过程用到了Collection基础对象,由于不是本文重点,暂时当成黑盒方法来看吧)。
public function dispatchToRoute(Request $request)
{
return $this->runRoute($request, $this->findRoute($request));
} //vendor\laravel\framework\src\Illuminate\Routing\Router.php
protected function runRoute(Request $request, Route $route)
{
//将返回路由的闭包函数设置在request对象中
$request->setRouteResolver(function () use ($route) {
return $route;
});
//绑定事件监听器
$this->events->dispatch(new Events\RouteMatched($route, $request)); return $this->prepareResponse($request,
$this->runRouteWithinStack($route, $request)
);
} protected function runRouteWithinStack(Route $route, Request $request)
{
//是否禁用中间件
$shouldSkipMiddleware = $this->container->bound('middleware.disable') &&
$this->container->make('middleware.disable') === true;
//获取系统路由中间件,
$middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route);
//跟之前一样的中间件调用方式
return (new Pipeline($this->container))
->send($request)
->through($middleware)
->then(function ($request) use ($route) {
//将执行结果包装成response响应对象
return $this->prepareResponse(
//进入控制器
$request, $route->run()
);
});
} //vendor\laravel\framework\src\Illuminate\Routing\Route.php
public function run()
{
$this->container = $this->container ?: new Container; try {
//以控制器形式执行
if ($this->isControllerAction()) {
return $this->runController();
}
//以闭包方式执行
return $this->runCallable();
} catch (HttpResponseException $e) {
return $e->getResponse();
}
}
开头我们提到了路由加载的时候还会进行一次中间件过滤,就是在这里了。在代码执行到runRoute方法的时候,会再次进行中间件过滤,这次的中间件是写在web数组中的系统中间件。并且这次通过中间件后,就会直接执行控制器,或是写在路由闭包中的代码了。返回的结果会被包装成响应对象依次返回,最终返回到浏览器。
控制器方法是由controllerDispatcher类来启动的这个类位于vendor\laravel\framework\src\Illuminate\Routing\ControllerDispatcher.php,它的dispatch方法接收了路由、控制器、方法三个参数。打印结果如下

这里的controller中会有一个默认的middleware数组,是因为中间件也可以在控制器中定义,不过我们一般直接定义在kernel中了。
再来看一下这段代码具体做了什么
public function dispatch(Route $route, $controller, $method)
{
//解析依赖
$parameters = $this->resolveClassMethodDependencies(
//获取路由中写定的参数
$route->parametersWithoutNulls(), $controller, $method
);
//判断是否有callAction方法。其实是在controller的父类中执行了call_user_func_array
if (method_exists($controller, 'callAction')) {
return $controller->callAction($method, $parameters);
}
//若没有则当成普通类进行调用返回
return $controller->{$method}(...array_values($parameters));
}
哦,差点忘了控制器的实例化是在Route类的runController方法中执行了getController
public function getController()
{
if (! $this->controller) {
//从路由中获取控制器字符串
$class = $this->parseControllerCallback()[0];
//通过字符串分割make控制器实例
$this->controller = $this->container->make(ltrim($class, '\\'));
} return $this->controller;
} protected function parseControllerCallback()
{
//laravel封装的字符串操作类,route的action中记录了我们写在route文件中的控制器相关字符串
return Str::parseCallback($this->action['uses']);
}
那么,到这里我们又梳理了一次从中间件到控制器的流程。中间件这一章差不多就是这些内容了。
laravel5.5源码笔记(六、中间件)的更多相关文章
- Tomcat8源码笔记(六)连接器Connector分析
根据 Tomcat8源码笔记(五)组件Container分析 前文分析,StandardService的初始化重心由 StandardEngine转移到了Connector的初始化,本篇记录下Conn ...
- laravel5.5源码笔记(五、Pipeline管道模式)
Pipeline管道模式,也有人叫它装饰模式.应该说管道是装饰模式的一个变种,虽然思想都是一样的,但这个是闭包的版本,实现方式与传统装饰模式也不太一样.在laravel的源码中算是一个比较核心的设计模 ...
- laravel5.5源码笔记(七、数据库初始化)
laravel中的数据库也是以服务提供者进行初始化的名为DatabaseServiceProvider,在config文件的providers数组中有写.路径为vendor\laravel\frame ...
- laravel5.5源码笔记(一、入口应用的初始化)
laravel的项目入口文件index.php如下 define('LARAVEL_START', microtime(true)); require __DIR__.'/../vendor/auto ...
- laravel5.5源码笔记(八、Eloquent ORM)
上一篇写到Eloquent ORM的基类Builder类,这次就来看一下这些方便的ORM方法是如何转换成sql语句运行的. 首先还是进入\vendor\laravel\framework\src\Il ...
- laravel5.5源码笔记(四、路由)
今天这篇博文来探索一下laravel的路由.在第一篇讲laravel入口文件的博文里,我们就提到过laravel的路由是在application对象的初始化阶段,通过provider来加载的.这个路由 ...
- laravel5.5源码笔记(三、门面类facade)
上次说了provider,那么这次来说说facade 首先是启动的源头,从laravel的kernel类中的$bootstrappers 数组,我们可以看到它的一些系统引导方法,其中的Register ...
- laravel5.5源码笔记(二、服务提供者provider)
laravel里所谓的provider服务提供者,其实是对某一类功能进行整合,与做一些使用前的初始化引导工作.laravel里的服务提供者也分为,系统核心服务提供者.与一般系统服务提供者.例如上一篇博 ...
- Tomcat8源码笔记(七)组件启动Server Service Engine Host启动
一.Tomcat启动的入口 Tomcat初始化简单流程前面博客介绍了一遍,组件除了StandardHost都有博客,欢迎大家指文中错误.Tomcat启动类是Bootstrap,而启动容器启动入口位于 ...
随机推荐
- APACHE如何一个站点绑定多个域名?
大家肯定遇到过这样的情况,需要APACHE2里一个站点绑定多个域名,那么如何操作呢?用ServerAlias 以前很笨,要使多个域名指向同一站点总是这样写: ServerAdmin admin@dom ...
- Sys.WebForms.PageRequestManagerParserErrorException: 常见的原因是:通过调用Response.Write()修改相应时,将启用响应筛选器、HttpModules或服务器追踪
Sys.WebForms.PageRequestManagerParserErrorException: 无法分析从服务器收到的消息,之所以出现此错误,常见的原因是:通过调用Response.Writ ...
- CAShapeLayer的使用[1]
CAShapeLayer的使用[1] 使用CoreAnimation绘制动画带来的系统开销非常的小,CoreAnimation通常都是使用GPU的. CAShapeLayer属于CoreAnimati ...
- Python实例---简单购物车Demo
简单购物车Demo # version: python3.2.5 # author: 'FTL1012' # time: 2017/12/7 09:16 product_list = ( ['Java ...
- windows系统镜像 微软官方资源便捷下载教程
今天跟小师弟学到了一个下载软件的好办法,省得到各种网站下载带有病毒,插件的资源. 这个神奇的网站叫做 MSDN, 我告诉你,这是一个私人维护的网站,里面有各种官方软件的下载地址.可以直接用下载工具 ...
- ZT --- extern "C"用法详解 2010-08-21 19:14:12
extern "C"用法详解 2010-08-21 19:14:12 分类: C/C++ 1.前言: 时常在cpp的代码之中看到这样的代码: #ifdef __cplusplus ...
- 【bzoj3456】 城市规划
题目 一句话题意,无向连通图计数 技不如人,甘拜下风 设\(f_i\)表示\(i\)个节点构成的无向连通图数量 之后...之后就不会了 于是抄题解 考虑容斥 \[f_i=t_i-\sum_{j=1}^ ...
- 【[HAOI2015]树上染色】
这道题真是非常神仙 第一眼看到题面肯定能想到状态是\(dp[i][j]\)表示\(i\)这棵子树里染了\(j\)个黑点的最大值 最大值? 什么最大值,之后就会发现这个样子完全没有办法转移 所以我们考虑 ...
- 【[AHOI2008]逆序对】
被锤爆了 被这个题搞得自闭了一上午,觉得自己没什么前途了 我又没有看出来这个题的一个非常重要的性质 我们填进去的数一定是单调不降的 首先如果填进去的数并不是单调不降的,那么填进去本身就会产生一些逆序对 ...
- Django中模型(五)
Django中模型(五) 六.模型查询 1.概述 查询集,表示从数据库获取的对象集合. 过滤器就是一个函数,基于所给的参数限制查询集结果.查询集可以有多个过滤器. 从sql角度来说,查询集合等价于se ...