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 协议》,转载必须注明作者和本文链接
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!