美文网首页
Laravel中的管道模式

Laravel中的管道模式

作者: 小虫跳跳 | 来源:发表于2019-05-22 16:42 被阅读0次

    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的写法。

    <?php
    
    class Pipeline {
    
        protected $passable;
    
        protected $pips = [];
            
        public function send($passable) {
            $this->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方法中调用pipline(this->passable)来循环递归执行,可以看到,destination被压到了堆栈的最里面。而排在越前面的pip反而会被压到最里面,最后执行,所以Laravel在传入pips数组时执行了array_reverse方法来确保排在前面的中间件能先被执行。

    完整版代码如下:

    <?php
    
    class Pipeline {
    
        protected $passable;
    
        protected $pips = [];
    
    
        public function send($passable) {
            $this->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中的管道模式

          本文链接:https://www.haomeiwen.com/subject/ohvuzqtx.html