Laravel中的管道模式

Laravel 框架在处理Middleware中采用了一种管道模式。相信有人一定对一下的代码好奇过,$next到底是什么?那么我就带大家来一探究竟。

namespace App\Http\Middleware;

use Closure;
use Illuminate\Support\Facades\Auth;

class RedirectIfAuthenticated
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @param  string|null  $guard
     * @return mixed
     */
    public function handle($request, Closure $next, $guard = null)
    {
        if (Auth::guard($guard)->check()) {
            return redirect('/home');
        }

        return $next($request);
    }
}

管道模式又称装饰模式,实际是装饰模式的一个变种,用闭包的形式来实现装饰模式。它可以像一条流水线一样传递并处理同一个对象,在每一步对对象进行不同的处理,同时每一个步骤又都是相互隔离的,耦合性不高,在需要对同一对象进行一系列业务分离的处理时非常有用。

先来看看Laravel启动时的核心代码,看看Laravel是如何使用管道模式的:

        /**
     * Send the given request through the middleware / router.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    protected function sendRequestThroughRouter($request)
    {
        $this->app->instance('request', $request);

        Facade::clearResolvedInstance('request');

        $this->bootstrap();

        return (new Pipeline($this->app))
                    ->send($request)
                    ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
                    ->then($this->dispatchToRouter());
    }

在通过路由之前,Laravel使用了管道模式来层层调用系统中注册的全局中间件来对Request进行处理。先通过Pipelinesend方法将需要传递进管道的对象注入,through方法设置需要通过的中间件。最后通过then来接收处理过的Request继续向路由传送。
Pipelinethen方法中是这样写的:

    /**
     * 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);
    }

核心方法就是这个array_reverse方法,这是一个php内置方法。文档上是这样写的:

array_reduce ( array $array , callable $callback [, mixed $initial = NULL ] ) : mixed

array_reduce() 将回调函数 callback 迭代地作用到 array 数组中的每一个单元中,从而将数组简化为单一的值。

参数 ¶


array : 输入的 array。

callbackcallback ( mixed $carry , mixed $item ) : mixed

carry:携带上次迭代里的值; 如果本次迭代是第一次,那么这个值是 initial。

item:携带了本次迭代的值。

initial:如果指定了可选参数 initial,该参数将在处理开始前使用,或者当处理结束,数组为空时的最后一个结果。

返回值 ¶


返回结果值。

initial 参数,array_reduce() 返回 NULL。

这个函数的作用是循环调取把array中的值传入第二个参数闭包函数中处理,有些像array_map函数。但它还会把处理结果返回传入下一次调用,依次迭代,第三个参数可选,如果传了会当作第一次调用的参数传入闭包函数中,如果最终返回值为空是还会返回intial的值。

Laravel在then方法中的作的事情就是把pips中存储的中间件依次调用carry方法处理,carry方法返回的是一个闭包函数,其作用是把中间件中的处理操作压成一个栈。然后传入array_reduce中去依次调用。我写了个简化版的Pipeline来展示carry的原理。

send方法,through方法和then方法还是仿照Laravel的写法。

passable = $passable;
        return $this;
    }

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

        
    protected function carry() {
        //压缩pips方法
    }
        
    public function then(Closure $destination) {
        $pipline = array_reduce(array_reverse($this->pips), $this->carry(), $this->prepareDestination($destination));
        return $pipline($this->passable);
    }
}

接下来我们重点来看carry方法的实现

    protected function carry() {
        //array_reduce使用的闭包
        return function($stack, $pip) {
            //array_reduce压缩起来的闭包函数栈是这一层
            return function($passable) use($stack, $pip) {
                return $pip->handle($passable, $stack);
            };
        };
    }

array_reduce第一次调用carry方法时,首先拿到的最外层的 return function($stack, $pip),函数把prepareDestination中的闭包函数当作$stack首先传入。把第一个pip暂且叫作pip1,传入$pip参数,返回结果展开来是这样:

        return function($passable) {
             return $pip1->handle($passable, , $destination); 
        };

第二次调用,将第一次的结果被当作了新的$stack$pip传入pip2,现在返回结果展开变成了这样:

        return function($passable) {
             return $pip2->handle($passable, , function($passable){
                 return $pip1->handle($passable, $destination);
             }); 
        };

依次递归,最后在then方法中调用this->passable)来循环递归执行,可以看到,destination被压到了堆栈的最里面。而排在越前面的pip反而会被压到最里面,最后执行,所以Laravel在传入pips数组时执行了array_reverse方法来确保排在前面的中间件能先被执行。

完整版代码如下:

passable = $passable;
        return $this;
    }

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

    protected function carry() {
        //array_reduce使用的闭包
        return function($stack, $pip) {
            //array_reduce压缩起来的闭包函数栈是这一层
            return function($passable) use($stack, $pip) {
                return $pip->handle($passable, $stack);
            };
        };

    }

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


    public function then(Closure $destination) {
        //通过array_reduce方法把pips压缩成闭包栈:
        //return function($passable) {
        //     return $pip1->handle($passable, function($passable) {
        //            return $pip2->handle($passable, function($passable) {
        //                   return $destination->handle($passable, function($passable){
        //                         //destination方法    
        //                   });
        //            });
        //     });
        //};
        //执行时通过传入handle的第二个闭包函数就可以层层调用:$next($passable);
        $pipline = array_reduce(array_reverse($this->pips), $this->carry(), $this->prepareDestination($destination));
        return $pipline($this->passable);
    }
}

你可能感兴趣的:(Laravel中的管道模式)