zoukankan      html  css  js  c++  java
  • laravel 管道设计模式

    1. laravel中的管道(Pipeline)是什么?

        所谓管道(Pipeline)设计模式,就是把数据传递给一个任务队列,由任务队列按次序依次对数据进行加工处理。在laravel框架中,这里的数据就是http请求,任务队列包含了一个又一个的中间件。

        类比1:以流水线或流水管道作类比,流水线上的产品(http请求),依次经过一个又一个的加工单元(对应一个又一个的中间件)进行处理,最后生成产品(http响应)。

        类比2:同样的,也可以与linux下的管道作类比,

      cat helloworld.txt | grep "hello world" | rev | > output.txt
        不过,差异的地方是,linux shell管道中任务队列中的单元是一个又一个的进程。而laravel框架中的Pipeline是运行在一个进程中的一个又一个的程序块,或者说逻辑片。
    2. laravel中如何使用pipeline?
        Laravel 在框架中的很多地方使用了管道设计模式,最常见的就是中间件的实现。
        当请求最终到达控制器动作被处理前,会先经过一系列的中间件。每个中间价都有一个独立的职责,例如,设置 Cookie、判断是否登录以及阻止 CSRF 攻击等等。每个阶段都会对请求进行处理,如果请求通过就会被传递给下一个处理,不通过就会返回相应的 HTTP 响应。
        这种机制使得我们很容易在请求最终到达应用代码前添加处理操作,当然如果不需要这个处理操作你也可以随时移除而不影响请求的生命周期。
    3. Pipeline有什么优点?
      1. 将复杂的处理流程分解成独立的子任务,从而方便测试每个子任务;
      2. 被分解的子任务可以被不同的处理进程复用,避免代码冗余。(这里说的不同的处理进程是指,针对不同的http请求,采用不同的子任务组合来处理)
      3. 在复杂进程中添加、移除和替换子任务非常轻松,对已存在的进程没有任何影响。
    4. Pipeline有什么缺点?
      1. 虽然每个子任务变得简单了,但是当你再度尝试将这些子任务组合成完整进程时有一定复杂性;
      2. 你还需要保证独立子任务测试通过后整体的流程能正常工作,这有一定的不确定性。(因为在管道中流动的是http请求,并且子任务可以修改http请求,这样就存在前一次的修改内容导致下一个子任务的执行失败的可能性)
      3. 当你看到的都是一个个子任务时,对理解整体流程带来困难(盲人摸象的故事想必大家很熟悉,正是此理)。
    5. 代码理解
      这里只局部分析管道实现的三个文件,它们并没有组成一个完整的工作流程。想要了解完整流程,还需要研究后面的几个文件。
      分析的文件:
      1.  laravel/vendor/laravel/framework/src/Illuminate/Contracts/Pipeline/Pipeline.php
      2.  laravel/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php
      3.  laravel/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php

      了解完整流程,还需要看以下文件:
      1.  laravel/public/index.php
      2.  laravel/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php
      3.  laravel/vendor/laravel/framework/src/Illuminate/Foundation/Application.php
      下面的部分是对Pipeline接口的定义,定义了send、through、via、then四个方法。(这里不知道via方法具体是干嘛的)

    laravelvendor/laravel/framework/src/Illuminate/Contracts/Pipeline/Pipeline.php

     1 <?php
     2 
     3 namespace IlluminateContractsPipeline;
     4 
     5 use Closure;
     6 
     7 interface Pipeline
     8 {
     9     /**
    10      * Set the traveler object being sent on the pipeline.
    11      *
    12      * @param  mixed  $traveler
    13      * @return $this
    14      */
    15     public function send($traveler);
    16 
    17     /**
    18      * Set the stops of the pipeline.
    19      *
    20      * @param  dynamic|array  $stops
    21      * @return $this
    22      */
    23     public function through($stops);
    24 
    25     /**
    26      * Set the method to call on the stops.
    27      *
    28      * @param  string  $method
    29      * @return $this
    30      */
    31     public function via($method);
    32 
    33     /**
    34      * Run the pipeline with a final destination callback.
    35      *
    36      * @param  Closure  $destination
    37      * @return mixed
    38      */
    39     public function then(Closure $destination);
    40 }
    View Code

        接下来,是对管道的实现.

    protected $container;  //保存服务容器的实例

    protected $passable;  //保存传入的http请求

    protected $pipes = [];  //保存子任务队列,子任务可以使闭包函数,也可以使类名与参数名的字符串组合

    protected $method = 'handle';  //当子任务是类名+参数的字符串组合时,$method指定在管道处理到该类子任务时,该类子任务用来处理http请求的方法名。$method默认是handle,但是可以通过                  via()方法修改

    在一次http的请求过程中,以下方法的被调用的过程是:send()  ->   through()  ->  then()  。

    public function send($passable){}  //传入初始的http请求

    public function through($pipes){}  //设置管道的子任务队列

    public function via($method){}  //设置$method的值

    public function then(Closure $destination){}  //启动管道,用设定的子任务队列去处理http请求

    protected function getSlice(){}      //本函数返回array_reduce()中所需要的第二个参数,callback函数

    protected function getInitialSlice(Closure $destination){}    //本函数对应array_reduce()中所需要的第三个参数,初始化值

    protected function parsePipeString($pipe){}    //针对子任务是类名+参数名的字符串组合,提取类名和参数

     由上面可知,整个管道的处理逻辑主要集中在then()方法中。

    then()方法中,最难懂的是下面这句话:

    1         return call_user_func(
    2             array_reduce($pipes, $this->getSlice(), $firstSlice), $this->passable
    3         );
    结合getSlice()方法,array_reduce()的处理过程实际上是,利用了闭包函数的特点,用闭包函数保存了局部作用域中的参数$stack和$pipe,并将保存了局部scope的闭包函数作为对象,压如由$stack保存的堆栈中,当将整个逆序的子任务队列的执行函数的闭包函数形式压入栈中后,再通过call\_user\_func(),传入$this->passable(即http请求),从栈中依次弹出闭包函数处理请求。

    laravelvendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php

      1 <?php
      2 
      3 namespace IlluminatePipeline;
      4 
      5 use Closure;
      6 use IlluminateContractsContainerContainer;
      7 use IlluminateContractsPipelinePipeline as PipelineContract;
      8 
      9 class Pipeline implements PipelineContract
     10 {
     11     /**
     12      * The container implementation.
     13      *
     14      * @var IlluminateContractsContainerContainer
     15      */
     16     protected $container;
     17 
     18     /**
     19      * The object being passed through the pipeline.
     20      *
     21      * @var mixed
     22      */
     23     protected $passable;
     24 
     25     /**
     26      * The array of class pipes.
     27      *
     28      * @var array
     29      */
     30     protected $pipes = [];
     31 
     32     /**
     33      * The method to call on each pipe.
     34      *
     35      * @var string
     36      */
     37     protected $method = 'handle';
     38 
     39     /**
     40      * Create a new class instance.
     41      *
     42      * @param  IlluminateContractsContainerContainer  $container
     43      * @return void
     44      */
     45     public function __construct(Container $container)
     46     {
     47         $this->container = $container;
     48     }
     49 
     50     /**
     51      * Set the object being sent through the pipeline.
     52      *
     53      * @param  mixed  $passable
     54      * @return $this
     55      */
     56     public function send($passable)
     57     {
     58         $this->passable = $passable;
     59 
     60         return $this;
     61     }
     62 
     63     /**
     64      * Set the array of pipes.
     65      *
     66      * @param  array|mixed  $pipes
     67      * @return $this
     68      */
     69     public function through($pipes)
     70     {
     71         $this->pipes = is_array($pipes) ? $pipes : func_get_args();
     72 
     73         return $this;
     74     }
     75 
     76     /**
     77      * Set the method to call on the pipes.
     78      *
     79      * @param  string  $method
     80      * @return $this
     81      */
     82     public function via($method)
     83     {
     84         $this->method = $method;
     85 
     86         return $this;
     87     }
     88 
     89     /**
     90      * Run the pipeline with a final destination callback.
     91      *
     92      * @param  Closure  $destination
     93      * @return mixed
     94      */
     95     public function then(Closure $destination)
     96     {
     97         $firstSlice = $this->getInitialSlice($destination);
     98 
     99         $pipes = array_reverse($this->pipes);
    100 
    101         return call_user_func(
    102             array_reduce($pipes, $this->getSlice(), $firstSlice), $this->passable
    103         );
    104     }
    105 
    106     /**
    107      * Get a Closure that represents a slice of the application onion.
    108      *
    109      * @return Closure
    110      */
    111     protected function getSlice()
    112     {
    113         return function ($stack, $pipe) {
    114             return function ($passable) use ($stack, $pipe) {
    115                 // If the pipe is an instance of a Closure, we will just call it directly but
    116                 // otherwise we'll resolve the pipes out of the container and call it with
    117                 // the appropriate method and arguments, returning the results back out.
    118                 if ($pipe instanceof Closure) {
    119                     return call_user_func($pipe, $passable, $stack);
    120                 } else {
    121                     list($name, $parameters) = $this->parsePipeString($pipe);
    122 
    123                     return call_user_func_array([$this->container->make($name), $this->method],
    124                             array_merge([$passable, $stack], $parameters));
    125                 }
    126             };
    127         };
    128     }
    129 
    130     /**
    131      * Get the initial slice to begin the stack call.
    132      *
    133      * @param  Closure  $destination
    134      * @return Closure
    135      */
    136     protected function getInitialSlice(Closure $destination)
    137     {
    138         return function ($passable) use ($destination) {
    139             return call_user_func($destination, $passable);
    140         };
    141     }
    142 
    143     /**
    144      * Parse full pipe string to get name and parameters.
    145      *
    146      * @param  string $pipe
    147      * @return array
    148      */
    149     protected function parsePipeString($pipe)
    150     {
    151         list($name, $parameters) = array_pad(explode(':', $pipe, 2), 2, []);
    152 
    153         if (is_string($parameters)) {
    154             $parameters = explode(',', $parameters);
    155         }
    156 
    157         return [$name, $parameters];
    158     }
    159 }
    View Code

     下面是对管道添加了异常处理的实现。

    laravelvendor/laravel/framework/src/Illuminate/Routing/Pipeline.php

     1 <?php
     2 
     3 namespace IlluminateRouting;
     4 
     5 use Closure;
     6 use Throwable;
     7 use Exception;
     8 use IlluminateHttpRequest;
     9 use IlluminateContractsDebugExceptionHandler;
    10 use IlluminatePipelinePipeline as BasePipeline;
    11 use SymfonyComponentDebugExceptionFatalThrowableError;
    12 
    13 /**
    14  * This extended pipeline catches any exceptions that occur during each slice.
    15  *
    16  * The exceptions are converted to HTTP responses for proper middleware handling.
    17  */
    18 class Pipeline extends BasePipeline
    19 {
    20     /**
    21      * Get a Closure that represents a slice of the application onion.
    22      *
    23      * @return Closure
    24      */
    25     protected function getSlice()
    26     {
    27         return function ($stack, $pipe) {
    28             return function ($passable) use ($stack, $pipe) {
    29                 try {
    30                     $slice = parent::getSlice();
    31 
    32                     return call_user_func($slice($stack, $pipe), $passable);
    33                 } catch (Exception $e) {
    34                     return $this->handleException($passable, $e);
    35                 } catch (Throwable $e) {
    36                     return $this->handleException($passable, new FatalThrowableError($e));
    37                 }
    38             };
    39         };
    40     }
    41 
    42     /**
    43      * Get the initial slice to begin the stack call.
    44      *
    45      * @param  Closure  $destination
    46      * @return Closure
    47      */
    48     protected function getInitialSlice(Closure $destination)
    49     {
    50         return function ($passable) use ($destination) {
    51             try {
    52                 return call_user_func($destination, $passable);
    53             } catch (Exception $e) {
    54                 return $this->handleException($passable, $e);
    55             } catch (Throwable $e) {
    56                 return $this->handleException($passable, new FatalThrowableError($e));
    57             }
    58         };
    59     }
    60 
    61     /**
    62      * Handle the given exception.
    63      *
    64      * @param  mixed  $passable
    65      * @param  Exception  $e
    66      * @return mixed
    67      *
    68      * @throws Exception
    69      */
    70     protected function handleException($passable, Exception $e)
    71     {
    72         if (! $this->container->bound(ExceptionHandler::class) || ! $passable instanceof Request) {
    73             throw $e;
    74         }
    75 
    76         $handler = $this->container->make(ExceptionHandler::class);
    77 
    78         $handler->report($e);
    79 
    80         $response = $handler->render($passable, $e);
    81 
    82         if (method_exists($response, 'withException')) {
    83             $response->withException($e);
    84         }
    85 
    86         return $response;
    87     }
    88 }
    View Code

     参考文献:

    1. Laravel 中管道设计模式的使用 —— 中间件实现原理探究

    2. 不依赖于任何框架的管道

  • 相关阅读:
    将Nginx添加到windows服务中
    springboot使用redis管理session
    GIT常用命令
    阻止360、谷歌浏览器表单自动填充
    谈谈对Spring IOC的理解
    同一个Nginx服务器同一端口配置多个代理服务
    LeetCode 653. Two Sum IV
    109. Convert Sorted List to Binary Search Tree(根据有序链表构造平衡的二叉查找树)
    108. Convert Sorted Array to Binary Search Tree(从有序数组中构造平衡的BST)
    LeetCode 236. Lowest Common Ancestor of a Binary Tree(二叉树求两点LCA)
  • 原文地址:https://www.cnblogs.com/jade640/p/6773125.html
Copyright © 2011-2022 走看看