Laravel7——一文读懂中间件源码
本文解决什么问题#
- 怎么理解中间件,为什么要中间件?
- 路由中间件和全局中间件有什么区别?
- 中间件的实现原理(源码解读)
什么是中间件#
先贴出一张图让大家理解中间件
再结合官方文档给出的定义:中间件提供了一种方便的机制来过滤进入应用程序的 HTTP 请求,就比较好理解了。
其实中间件也可以这么理解①类似于设计模式的装饰器模式,意义在于我们可以在不改变核心 / 原有的功能的情况下,为其添加其他的功能;②面向对象中抽象封装的思想,将相同的功能全部抽象封装起来;
现在我们再带着第 2、3 问题,全局与路由中间件的区别,中间件的实现原理,下面来看源码分析。
中间件源码逐步解读#
生命周期的概括#
Laravel 程序的生命周期就是从客户端接受到数据,然后处理数据返回一个响应的过程;
Laravel 应用的所有请求入口都是 public/index.php 文件。
// 文件 public/index.php
// 1. 引入composer自动加载功能
require __DIR__.'/../vendor/autoload.php';
// 2. 实例化了服务容器$app
$app = require_once __DIR__.'/../bootstrap/app.php';
// 3. 从服务容器中
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
// 4. 将获取的请求数据作为参数,实例化了一个Illuminate\Http\Request的实例;
// 简单的可以理解为, 将$_GET $_POST $_SERVER 等php能获取到的数据进行处理
$request = Illuminate\Http\Request::capture()
// 5. 处理 请求实例$request 得到 响应实例 $response
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);
从上述的功能简单可以看出,我们中间件的处理是在第 5 步,因为中间件其实就是对 request 请求进行处理的嘛
ps:此文着重讲解中间件的实现,以下贴出的源码会对次要代码省略,也会淡化其他功能(如:服务容器,Facade),以方便大家理解;
中间件实现相关源码#
// 文件 vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php
public function handle($request)
{
try {
$response = $this->sendRequestThroughRouter($request);
}
return $response;
}
// 文件 vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php
protected function sendRequestThroughRouter($request)
{
return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
}
// 文件 vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php
// 管道类
class Pipeline implements PipelineContract
{
protected $container;
protected $passable;
protected $pipes = [];
protected $method = 'handle';
public function __construct(Container $container = null)
{
$this->container = $container;
}
// 把$request实例放入到存到passable属性内
public function send($passable)
{
$this->passable = $passable;
return $this;
}
// 把middleware存入到pipes属性内
public function through($pipes)
{
$this->pipes = is_array($pipes) ? $pipes : func_get_args();
return $this;
}
public function then(Closure $destination)
{
$pipeline = array_reduce(
array_reverse($this->pipes()), $this->carry(), $this->prepareDestination($destination)
);
return $pipeline($this->passable);
}
protected function prepareDestination(Closure $destination)
{
return function ($passable) use ($destination) {
try {
return $destination($passable);
} catch (Throwable $e) {
return $this->handleException($passable, $e);
}
};
}
protected function carry()
{
return function ($stack, $pipe) {
return function ($passable) use ($stack, $pipe) {
try {
if (is_callable($pipe)) {
return $pipe($passable, $stack);
} elseif (! is_object($pipe)) {
[$name, $parameters] = $this->parsePipeString($pipe);
$pipe = $this->getContainer()->make($name);
$parameters = array_merge([$passable, $stack], $parameters);
} else {
$parameters = [$passable, $stack];
}
$carry = method_exists($pipe, $this->method)
? $pipe->{$this->method}(...$parameters)
: $pipe(...$parameters);
return $this->handleCarry($carry);
} catch (Throwable $e) {
return $this->handleException($passable, $e);
}
};
};
}
}
以上就是从 index 入口文件,到实现中间件功能的核心源码;从上面的第二段代码可以看出,中间件实现的核心功能很简单,就是 Pipeline 实例的三个链式调用;所以着重分析,Pipeline 里面三个 function 做了什么事,如何实现前后置中间件?
send()/through()#
其实不难看出,这两个方法只是简单的进行属性设置。①把 $request 实例放入到存到 passable 属性内;②把 middleware 中间件数组存入到 pipes 属性内。不用过多解释
then()#
由于篇幅限制,下面另起一文
手把手带你理解中间件 Class Pipeline - Function then () 实现原理
路由中间件与全局中间件#
通过我们对 then 方法的了解,我们知道,then 方法传入的闭包 $this->dispatchToRouter () 是在前置全局中间件和后置中间件的中间进行执行的。
那么根据一层一层查看调用最会发现
// 文件 vendor/laravel/framework/src/Illuminate/Routing/Router.php
protected function runRouteWithinStack(Route $route, Request $request)
{
... ...
return (new Pipeline($this->container))
->send($request)
->through($middleware)
->then(function ($request) use ($route) {
return $this->prepareResponse(
$request, $route->run()
);
});
}
其实路由中间件的实现和全局中间件的实现方式没有区别… …
总结#
添加中间件的整个过程,就是通过 send 方法传入请求,用 though 方法传入全局中间件,最后用 then 方法实现了一个递归的闭包函数,最终执行的过程。
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: