手把手带你理解中间件Pipeline_function then()原理

Laravel框架
452
0
0
2022-05-10
标签   Laravel进阶

Pipeline类中function then()的实现原理

// 文件 vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php
// 管道类
class Pipeline implements PipelineContract
{
    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);
                }
            };
        };
    }

}

要理解Pipeline类中then实现中间件的原理需要着重了解一下重点:

  1. 内置函数:array_reduce()
  2. 闭包类型:Closure
  3. 内置函数:array_reverse()

下面手把手带你实现下面的功能:

手把手带你理解中间件Pipeline_function then()原理

then()功能分析实现

我们可以通过dd($this->pipes())打印发现,$this->pipes()是一个中间件的数组

手把手带你理解中间件Pipeline_function then()原理

$this->carry() 和 $destination都是一个闭包

步骤一 :

那么我们来简单模仿一下写个简单的方法来先了解下array_reduce()

先写一个简单的控制器:

// 文件 /routes/web.php
Route::get('/','TestController@index');

// 文件 /app/Http/Controllers/TestController.php
public function index()
{
    $funcA = function () {
        echo "funcA <br/>";
    };

    $funcB = function () {
        echo "funcB <br/>";
    };

    $funcC = function () {
        echo "funcC <br/>";
    };
    $pipes = [$funcA, $funcB, $funcC];
    // $carry 是上一个函数的返回值; 若一开是第三个参数为空时 则为null  
    // $item 数组的元素值  
    // 此处我们不考虑$carry 我们让他每次都运行 闭包$item()  
    array_reduce($pipes, function ($carry, $item) {
        $item();
    });
}

结果:手把手带你理解中间件Pipeline_function then()原理

步骤二

现在我们再修改一下,此时我们闭包内不执行$item,而是作为$carry值返回给下一次循环作为参数调用;并且加入第三个参数,为$carry赋一个默认值:

public function index()
{

    ... ... // 前面内容不变

        // 第三个参数为一个 字符串 , 这个值会作为第一次调用时,$carry的值  
    $finalFunc = array_reduce($pipes, function ($carry, $item) {
        // 如果是字符串则直接打印  
        if (is_string($carry)) {
            echo $carry;
        } elseif ($carry instanceof \Closure) {
            // 如果是闭包则运行  
            $carry();
        }
        // 向下一个调用的$carry返回当前的闭包$item;  
        return $item;
    }, "Init<br/>");

    // 三次循环的参数与返回如下:  
    // 第一次参数:$carry= "Init<br/>"; $item=closure funcA() 返回 closure funcA()  
    // 第二次参数:$carry= closure funcA(); $item=closure funcB() 返回 closure funcB()  
    // 第三次参数:$carry= closure funcB(); $item=closure funcC() 返回 closure funcC()

    // 此时的返回值为 closure funcC() 直接调用就是调用funcC  
    $finalFunc();
}

运行结果:手把手带你理解中间件Pipeline_function then()原理

我们好像发现,离按顺序执行中间件已经越来越近了,那我们继续修改。

步骤三

改变如下

public function index()
{
    // 1. 为闭包添加参数,  
    $middelwareA = function ($request, \Closure $next) {
        echo "middlewareA hanlde request <br/>";
    };
    $middlewareB = function ($request, \Closure $next) {
        echo "middlewareB hanlde request <br/>";
    };
    $middlewareC = function ($request, \Closure $next) {
        echo "middlewareC hanlde request <br/>";
    };

    $pipes = [$middelwareA, $middlewareB, $middlewareC];

    // 2. 增加一个变量request  
    $request = 'request';
    // 3. 增加一个闭包,模仿源码的中的参数$destination  
    $callback = function ($request) {
        echo "callback handler request <br/>";
    };
    // 4. 改变第三个参数,传入一个闭包(下面添加了方法一个新的方法来返回闭包)  
    $finalFunc = array_reduce($pipes, $this->carry(),$this->prepareDestination($callback));

    // 运行解释: 此时最终返回的是一个闭包 closure funcC($request,$carry)  
    // 运行解释: 因为前面的callback funcA funcB 我们都没做任何处理的直接丢掉了  
    $finalFunc($request);
}

// 6. 内容改简单一点,直接调用$item()
public function carry()
{
    return function ($carry, $item) {
        return function ($request) use ($carry, $item) {
            // 第一次参数:$carry= closure $callback($request); $item=closure funcA() 返回 closure($request,$carry)  
            // 第二次参数:$carry= closure funcA($request,$carry); $item=closure funcB() 返回 $item=closure funcB($request,$carry)  
            // 第二次参数:$carry= closure funcB($request,$carry); $item=closure funcC() 返回 $item=closure funcC($request,$carry)  
            return $item($request, $carry);
            //                 $item($request, $carry);
        };
    };
}

public function prepareDestination($callback)
{
    return function ($request) use ($callback) {
        return $callback($request);
    };
}

运行结果 : middlewareC hanlde request

解释如代码注释所示

我们再微调一下代码

步骤四
 public function index()
    {
        // 1. 为闭包添加参数,  
        $middelwareA = function ($request, \Closure $next) {
            echo "middlewareA hanlde request <br/>";
            // 此处的$next = closure $callback($request)  
            return $next($request);
        };
        $middlewareB = function ($request, \Closure $next) {
            echo "middlewareB hanlde request <br/>";
            // 此处的$next = closure funcA($request,closure $callback($request))  
            return $next($request);
        };
        $middlewareC = function ($request, \Closure $next) {
            echo "middlewareC hanlde request <br/>";
            // 此处的$next = closure funcB($request,closure funcA(...))  
            return $next($request);
        };

        ... ... // 中间代码省略

        $finalFunc($request);
    }

结果:手把手带你理解中间件Pipeline_function then()原理

好的,中间件的原理实现的差不多了。

我们想想如何实现前后置中间件

步骤五
public function index()
{
    $middelwareA = function ($request, \Closure $next) {
        echo "before middlewareA hanlde request <br/>";
        // 此处的$next = closure $callback($request)  
        $response = $next($request);
        echo "after middlewareA hanlde request <br/>";
        return $response;
    };
    $middlewareB = function ($request, \Closure $next) {
        echo "before middlewareB hanlde request <br/>";
        // 此处的$next = closure funcA($request,closure $callback($request))  
        $response = $next($request);
        echo "after middlewareB hanlde request <br/>";
        return $response;
    };

    $middlewareC = function ($request, \Closure $next) {
        echo "before middlewareC hanlde request <br/>";
        // 此处的$next = closure funcB($request,closure funcA(...))  
        $response = $next($request);
        echo "after middlewareC hanlde request <br/>";
        return $response;
    };

    ... ... // 中间代码省略

    $finalFunc($request);
}

运行结果:

手把手带你理解中间件Pipeline_function then()原理

至此,大家应该了解了then是如何实现中间件的原理的了吧.(关于中间件的执行顺序就留给大家自己思考吧)