Laravel7——一文读懂中间件源码

本文解决什么问题#

  1. 怎么理解中间件,为什么要中间件?
  2. 路由中间件和全局中间件有什么区别?
  3. 中间件的实现原理(源码解读)

什么是中间件#

先贴出一张图让大家理解中间件

Laravel
再结合官方文档给出的定义:中间件提供了一种方便的机制来过滤进入应用程序的 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 协议》,转载必须注明作者和本文链接
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。