管道模式执行全局中间件

Http类的run方法

    public function run(Request $request = null): Response
    {
        //自动创建request对象
        $request = $request ?? $this->app->make('request', [], true);
        $this->app->instance('request', $request);

        try {
            // 得到 Response 类对象
            $response = $this->runWithRequest($request);
        ...
    }

runWithRequest 方法

    protected function runWithRequest(Request $request)
    {
        ...
        // 执行了路由获得 Response 实例,其间经过全局中间件过滤
        return $this->app->middleware->pipeline()
            ->send($request)
            ->then(function ($request) {
                return $this->dispatchToRoute($request);
            });
    }

框架中的全局中间件默认都是注释起来的,现在我们打开两个

<?php
// 全局中间件定义文件
return [
    // 全局请求缓存
    // \think\middleware\CheckRequestCache::class,
    // 多语言加载
    \think\middleware\LoadLangPack::class,
    // Session初始化
    \think\middleware\SessionInit::class,
    // 页面Trace调试
    // \think\middleware\TraceDebug::class,
];

$this->app->middleware->pipeline() 实例化 middleware 类,执行 middleware 的 pipeline 方法

public function pipeline(string $type = 'global')
{
    return (new Pipeline())
        ->through(array_map(function ($middleware) {
            return function ($request, $next) use ($middleware) {
                [$call, $param] = $middleware;
                if (is_array($call) && is_string($call[0])) {
                    $call = [$this->app->make($call[0]), $call[1]];
                }
                $response = call_user_func($call, $request, $next, $param);

                if (!$response instanceof Response) {
                    throw new LogicException('The middleware must return Response instance');
                }
                return $response;
            };
        }, $this->sortMiddleware($this->queue[$type] ?? [])))
        ->whenException([$this, 'handleException']);
}

这里就是返回了一个 pipeline 类的实例,同时调用 pipeline 的 through 方法

public function through($pipes)
{
    $this->pipes = is_array($pipes) ? $pipes : func_get_args();
    return $this;
}

结果是 pipeline 类实例的 pipes 属性保存了一个数组,数组中的每个元素都是一个闭包函数,闭包函数内执行了中间件类的 handle 方法

pipeline 的 then 方法

public function then(Closure $destination)
{
    $pipeline = array_reduce(
        array_reverse($this->pipes),
        $this->carry(),
        function ($passable) use ($destination) {
            try {
                return $destination($passable);
            } catch (Throwable | Exception $e) {
                return $this->handleException($passable, $e);
            }
        });

    return $pipeline($this->passable);
}

我们把 carry 方法的代码合并过来,去掉异常捕捉的代码,看下

$pipeline = array_reduce(
    array_reverse($this->pipes),
    function ($stack, $pipe) {
        return function ($passable) use ($stack, $pipe) {
            return $pipe($passable, $stack);
        }
    },
    function ($passable) use ($destination) {
        return $destination($passable);
    });

前面我们打开了两个全局中间件,pipes 数组中就有两个闭包, array_reduce 会迭代两次,我们来看下第一次迭代
此时 $stack 的初始值是 array_reduce 的第三个参数,我们命名为 A

(A)

function ($passable) use ($destination) {
    return $destination($passable);
};

array_reduce 第二个参数的返回值是 B

(B)

function ($passable) use ($stack, $pipe) {
    return $pipe($passable, $stack);
}

将 A 代入 B,得到第一次迭代的返回值 C,也是第二次迭代的 $stack 的值

(C)

function ($passable) use ($stack, $pipe) {
    return $pipe($passable, function ($passable) use ($destination) {
        return $destination($passable);
    });
}

第二次迭代,将 C 代入 B,得到 D

(D)

function ($passable) use ($stack, $pipe) {
    return $pipe($passable, function ($passable) use ($stack, $pipe) {
        return $pipe($passable, function ($passable) use ($destination) {
            return $destination($passable);
        });
    });
}

最终 $pipeline 就是 D 这样一个超级闭包,这里 use 的两个参数 $stack 已经更换成了相关代码, $pipe 是执行中间件 handle 方法的闭包

或者我们可以这样理解这个闭包 D,当然 handle 方法不是静态的,明白意思就好

function ($passable) {
    return LoadLangPack::handle($passable, function ($passable) {
        return SessionInit::handle($passable, function ($passable) use ($destination) {
            return $destination($passable);
        });
    });
}

$destination($passable) 方法就是执行路由,返回 response

中间件代码的执行顺序

handle 方法的代码大概是这样的

public function handle($request, Closure $next)
{
    // 前置操作

    $response = $next($request);

    // 后置操作

    return $response;
}

我们这里的实际执行顺序就是 LoadLangPack::handle 前置操作 -> SessionInit::handle 前置操作 -> 执行路由 -> SessionInit::handle 后置操作 -> LoadLangPack::handle 后置操作

本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

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