Laravel 中间件处理的核心机制 Pipeline 关键分析

Illuminate/Pipeline/Pipeline.php

/**
     * Run the pipeline with a final destination callback.
     *
     * @param  \Closure  $destination
     * @return mixed
     */
    public function then(Closure $destination)
    {
        $pipeline = array_reduce(
            array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination)
        );

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

    /**
     * Get the final piece of the Closure onion.
     *
     * @param  \Closure  $destination
     * @return \Closure
     */
    protected function prepareDestination(Closure $destination)
    {
        return function ($passable) use ($destination) {
            return $destination($passable);
        };
    }

    /**
     * Get a Closure that represents a slice of the application onion.
     *
     * @return \Closure
     */
    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];
                }

                $response = method_exists($pipe, $this->method)
                                ? $pipe->{$this->method}(...$parameters)
                                : $pipe(...$parameters);

                return $response instanceof Responsable
                            ? $response->toResponse($this->container->make(Request::class))
                            : $response;
            };
        };
    }
  1. 管道处理的核心方法then,实际上是一个非常有用的PHP函数array_reduce($array, $callback, $initial)的应用,只不过待处理数组变成了可调用的中间件函数(字符串或closure表示的),这些函数都只接受一个参数即Request实例;
  2. 处理函数因为有额外参数$passable(即 Request实例)需要传入,所以多包了一层闭包函数【注:关于多层包含的闭包函数,在Python装饰器原理中可以充分理解,尤其是阮一峰的逐步递进写完善的装饰器过程】;
  3. 初始值也是一个闭包函数,这个是真正的response响应函数。

$this->method = 'handle' 这个方法名,可通过via函数修改。

中间件类方法 handle($request, Closure $next) 有2个参数,这个对应闭包处理过后的中间件函数的返回值

return $pipe($passable, $stack)。

Laravel中间件逻辑,是闭包函数强大功能的一大体现。于是我们使用闭包函数,以函数式编程风格实现简化版的中间件流程:

function f1($f){
    return function($x) use ($f){
        echo 'middleware 1 begin.'.PHP_EOL;
        $x += 1;
        $x = $f($x);
        echo 'middleware 1 end.'.PHP_EOL;
        return $x;  
    };

}

function f2($f){
    return function($x) use($f){
        echo 'middleware 2 begin: '.PHP_EOL;
        $x += 2;
        $x = $f($x);
        echo 'middleware 2 end.'.PHP_EOL;
        return $x;
    };

}

function respond(){
    return function($x){
        echo 'Generate some response.'.PHP_EOL;
        return $x;
    };
}

$x = 1;
$response = f2(f1(respond()))($x);
echo $response;

输出:
middleware 2 begin:
middleware 1 begin.
Generate some response.
middleware 1 end.
middleware 2 end.
4

可以看出为什么Laravel的Pipeline的实现需要反转中间件数组。

高阶函数的实现,可以通过层层传递"函数名字符串"的原始方法来实现,但是通过闭包函数实现更加简单。
array_reduce写法

$f1 = function($x, $f){
    echo 'middleware 1 begin.'.PHP_EOL;
    $x += 1;
    $x = $f($x);
    echo 'middleware 1 end.'.PHP_EOL;
    return $x;  
};

$f2 = function($x, $f){
    echo 'middleware 2 begin: '.PHP_EOL;
    $x += 2;
    $x = $f($x);
    echo 'middleware 2 end.'.PHP_EOL;
    return $x;
};

$respond = function($x){
    echo 'Generate some response.'.PHP_EOL;
    return $x;
};

$middlewares = [$f1, $f2];
$initial = $respond;
$foo = array_reduce($middlewares, function($stack, $item){
    return function($request) use ($stack, $item){
        return $item($request, $stack);
    };
}, $initial);

$x = 1;
echo $foo($x);

输出:
middleware 2 begin:
middleware 1 begin.
Generate some response.
middleware 1 end.
middleware 2 end.
4

本作品采用《CC 协议》,转载必须注明作者和本文链接
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

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