Pipeline管道模式,也有人叫它装饰模式。应该说管道是装饰模式的一个变种,虽然思想都是一样的,但这个是闭包的版本,实现方式与传统装饰模式也不太一样。在laravel的源码中算是一个比较核心的设计模式了。
管道模式,或者说装饰模式的思想,就是在不改变原有程序的基础上,可以方便的在已有程序上添加新的功能。

在说管道模式之前让我们看一下array_reduce这个函数

  1. mixed array_reduce ( array $array , callable $callback [, mixed $initial = NULL ] )
  2.  
  3. array_reduce() 将回调函数 callback 迭代地作用到 array 数组中的每一个单元中,从而将数组简化为单一的值。
  4.  
  5. array
  6. 输入的 array
  7.  
  8. callback
  9. mixed callback ( mixed $carry , mixed $item )
  10. carry
  11. 携带上次迭代里的值; 如果本次迭代是第一次,那么这个值是 initial
  12.  
  13. item
  14. 携带了本次迭代的值。
  15.  
  16. initial
  17. 如果指定了可选参数 initial,该参数将在处理开始前使用,或者当处理结束,数组为空时的最后一个结果。

以上是php官网对这个函数的解释,看的一头雾水对不对?没关系,我们先来一个demo

  1. //首先我们有一个数组
  2. $a = array(1, 2, 3, 4, 5);
  3. $x = array();
  4.  
  5. //我们输出了array_reduce的结果为15,这个函数传递了两个参数,第一个就是上面的数组a,我们重点来看第二个闭包函数
  6. var_dump(array_reduce($a, "sum")); // int(15)
  7.  
  8. //array_reduce的第三个参数会在循环开始的时候当做闭包函数的第一个carry值传入
  9. var_dump(array_reduce($a, "product", 10)); // int(1200), because: 10*1*2*3*4*5
  10.  
  11. //当array_reduce处理结束后,若结果为空,也会将第三个参数返回
  12. var_dump(array_reduce($x, "sum", "No data to reduce")); // string(17) "No data to reduce"
  13.  
  14. //这个闭包函数接收了两个参数
  15. function sum($carry, $item)
  16. {
  17. echo 'before___carry:'.$carry.'<br><br>';
  18. echo 'before___item:'.$item.'<br><br>';
  19.  
  20. //这个函数只是简单的对两个参数做了加法
  21. $carry += $item;
  22.  
  23. echo 'after___carry:'.$carry.'<br><br>';
  24. echo '<br><br>';
  25. return $carry;
  26. }
  27.  
  28. function product($carry, $item)
  29. {
  30. echo 'before___carry:'.$carry.'<br><br>';
  31. echo 'before___item:'.$item.'<br><br>';
  32.  
  33. $carry *= $item;
  34.  
  35. echo 'after___carry:'.$carry.'<br><br>';
  36. echo '<br><br>';
  37. return $carry;
  38. }

大家在运行过这个demo之后,便会发现,array_reduce只是一个循环函数。但是,它和foreach不一样的地方在于,它的闭包处理函数所接收的两个参数。其中的$item参数会在每次循环的时候代入数组的各项值,而$carry就比较专一了,它只接收上一次循环中自己的返回值。不过专一也只是相对的,$carray还接收array_reduce传入的第三个参数作为闭包开始循环前$carray的默认值,或是循环结束后返回值为null时的默认值。我在代码中已经把变量各时期的值给打印出来了,相信大家一看就会明白。这个函数最典型的例子就是在做累计运算的时候,我们不需要向foreach循环那样创建一个变量了。(大家都知道给变量起名字是一件很麻烦的事情)

ok,函数介绍完了,可是这和管道模式又有什么关系呢?不知道大家注意到没有,在array_reduce的闭包函数进行循环的时候,array_reduce的第三个参数从闭包的$array进入,从return返回,紧接着再次从$array中进入,而每次循环的时候,都会从$item中获取到外部进行运算。这个过程是不是很像数据在一个螺旋管道中流通,同时不断的向其中添加新的操作,并且没有改变原先的程序代码,降低了程序间的耦合度。

接下来进行管道模式

请求处理管道的思维导图

思维导图看的还不够清晰?ok,接着举例子,看demo

从图中我们可以知道类Request都具有相同的接口,那么我们可以约定一个接口规范

  1. interface RequestInterface
  2. {
  3. // 我们之前讲过装饰者模式,这里是通过闭包函数实现
  4. // 通过之后实现类及调用就可以看出
  5. public static function handle(Closure $next);
  6. }

接口有了那么我们就遵循接口开始实现

  1. class Request1 implements RequestInterface
  2. {
  3. public static function handle(Closure $next)
  4. {
  5. echo "Request1 Begin." . "<br />";
  6. $next();
  7. echo "Request1 End." . "<br />";
  8. }
  9. }
  10.  
  11. class Request2 implements RequestInterface
  12. {
  13. public static function handle(Closure $next)
  14. {
  15. echo "Request2 Begin." . "<br />";
  16. $next();
  17. echo "Request2 End." . "<br />";
  18. }
  19. }
  20.  
  21. class Request3 implements RequestInterface
  22. {
  23. public static function handle(Closure $next)
  24. {
  25. echo "Request3 Begin." . "<br />";
  26. $next();
  27. echo "Request3 End." . "<br />";
  28. }
  29. }
  30.  
  31. class Request4 implements RequestInterface
  32. {
  33. public static function handle(Closure $next)
  34. {
  35. echo "Request4 Begin." . "<br />";
  36. $next();
  37. echo "Request4 End." . "<br />";
  38. }
  39. }

四种请求处理过程我们均已实现,为了简化都是打印一句话,可以在浏览器上直观显示流程;
我们还需要一个客户端来发出请求

  1. class Client
  2. {
  3. // 这里包含了所有的请求
  4. private $pipes = [
  5. 'Request1',
  6. 'Request2',
  7. 'Request3',
  8. 'Request4',
  9. ];
  10.  
  11. // 这里就是思维导图中默认返回的匿名回调函数
  12. private function defaultClosure()
  13. {
  14. return function () {
  15. echo '请求处理中...' . "<br />";
  16. };
  17. }
  18.  
  19. // 这里就是整个请求处理管道的关键
  20. private function getSlice()
  21. {
  22. return function ($stack, $pipe)
  23. {
  24. return function () use ($stack, $pipe)
  25. {
  26. return $pipe::handle($stack);
  27. };
  28. };
  29. }
  30.  
  31. // 这里是负责发起请求处理
  32. public function then()
  33. {
  34. call_user_func(array_reduce($this->pipes, $this->getSlice(), $this->defaultClosure()));
  35. }
  36. }

当我们调用时:

  1. $worker = new Client();
  2. $worker->then();

浏览器就会显示:

那么我来解释一下整个流程吧,在代码注释说了getSlice是关键,那么我们来解读一下代码

解析代码

client的then方法,call_user_func是执行一个函数的api,它的参数是一个闭包函数。而这个参数则是我们文章一开始提到的array_reduce这个循环的最终返回值。

我们都还记得这个函数一共有三个参数:

第一个参数代表从外界加入到管道中的附加操作

第二个参数则是执行加工的工厂

第三个参数则是进入管道的程序数据源

先看第三个参数数据源

  1. // 这里就是思维导图中默认返回的匿名回调函数
  2. private function defaultClosure()
  3. {
  4. //这个return出去的闭包就是我们的数据源
  5. return function () {
  6. echo '请求处理中...' . "<br />";
  7. };
  8. }
  1.  然后对程序进行装饰的装饰品数组
  1. // 这里包含了所有的请求
  2. private $pipes = [
  3. 'Request1',
  4. 'Request2',
  5. 'Request3',
  6. 'Request4',
  7. ];

这里虽然只是个数组,但它在刚刚的循环闭包中被当做对象名来使用,通过名称调度不同的类

然后是循环闭包

  1. // 这里就是整个请求处理管道的关键
  2. private function getSlice()
  3. {
  4. //这个函数我们的闭包循环函数
  5. return function ($stack, $pipe)
  6. {
  7. //而这里便是我们最开始例子中的return返回值,只不过刚开始的时候我们返回的是一个数字变量,这里变成了另一个闭包
  8. return function () use ($stack, $pipe)
  9. {
  10. return $pipe::handle($stack);
  11. };
  12. };
  13. }
  1. 这里的循环闭包返回值也是一个闭包,在不断的循环中,闭包函数就像洋葱一样,一层一层不断包裹。
  2.  
  3. 如此类推,最终array_reduce返回一个匿名函数,这就像上面思维导图最终所描述的一样
  1. function (){
  2. return Request4::handle(function (){
  3. return Request3::handle(function (){
  4. return Request2::handle(function (){
  5. return Reuqest1::handle(function (){
  6. echo '请求处理中...' . "<br />";
  7. });
  8. });
  9. });
  10. });
  11. }

所以当调用call_user_func时就是执行array_reduce返回的匿名函数,我们从各个请求处理类的handle方法也得知,会直接调用传入的匿名函数,注意顺序即可理解浏览器的输出。

与装饰者模式的关系

这里没有用到类来对实例对象进行包装,而是通过闭包函数完成,每一次处理在上一次处理之上进行包装,最后获得响应,就像流水线一样,一个请求进来通过一道道工序加工(包装)最后生成响应。

理解了这几个demo,那么laravel里中间件最重点的部分你就已经理解了。

最后,本文内容并非全部原创,部分图片与代码来源:http://blog.chenjunwu.cn/2017/05/02/Request-pipeline

laravel5.5源码笔记(五、Pipeline管道模式)的更多相关文章

  1. Tomcat8源码笔记(五)组件Container分析

    Tomcat8源码笔记(四)Server和Service初始化 介绍过Tomcat中Service的初始化 最先初始化就是Container,而Container初始化过程是咋样的? 说到Contai ...

  2. laravel5.5源码笔记(一、入口应用的初始化)

    laravel的项目入口文件index.php如下 define('LARAVEL_START', microtime(true)); require __DIR__.'/../vendor/auto ...

  3. laravel5.5源码笔记(七、数据库初始化)

    laravel中的数据库也是以服务提供者进行初始化的名为DatabaseServiceProvider,在config文件的providers数组中有写.路径为vendor\laravel\frame ...

  4. laravel5.5源码笔记(六、中间件)

    laravel中的中间件作为一个请求与响应的过滤器,主要分为两个功能. 1.在请求到达控制器层之前进行拦截与过滤,只有通过验证的请求才能到达controller层 2.或者是在controller中运 ...

  5. laravel5.5源码笔记(二、服务提供者provider)

    laravel里所谓的provider服务提供者,其实是对某一类功能进行整合,与做一些使用前的初始化引导工作.laravel里的服务提供者也分为,系统核心服务提供者.与一般系统服务提供者.例如上一篇博 ...

  6. laravel5.5源码笔记(八、Eloquent ORM)

    上一篇写到Eloquent ORM的基类Builder类,这次就来看一下这些方便的ORM方法是如何转换成sql语句运行的. 首先还是进入\vendor\laravel\framework\src\Il ...

  7. laravel5.5源码笔记(四、路由)

    今天这篇博文来探索一下laravel的路由.在第一篇讲laravel入口文件的博文里,我们就提到过laravel的路由是在application对象的初始化阶段,通过provider来加载的.这个路由 ...

  8. laravel5.5源码笔记(三、门面类facade)

    上次说了provider,那么这次来说说facade 首先是启动的源头,从laravel的kernel类中的$bootstrappers 数组,我们可以看到它的一些系统引导方法,其中的Register ...

  9. Tomcat8源码笔记(七)组件启动Server Service Engine Host启动

    一.Tomcat启动的入口 Tomcat初始化简单流程前面博客介绍了一遍,组件除了StandardHost都有博客,欢迎大家指文中错误.Tomcat启动类是Bootstrap,而启动容器启动入口位于 ...

随机推荐

  1. Css3中拖拽效果的实例(带有注释~欢迎指教)

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  2. 小米OJ 有多少个等差数列

    题目链接 https://code.mi.com/problem/list/view?id=20 代码 #include <bits/stdc++.h> using namespace s ...

  3. js的一道经典题目

    今天碰到一道题,里面既包含了匿名函数的知识,也包含了预编译,函数的传参(形参),感觉迷迷糊糊的,所以想着做个总结. var foo={n:1}; (function(foo){ console.log ...

  4. php测试工具

    如果是测压力有apache的ab如果要看性能则有xdebug和xhprof.还有linux的strace命令来跟踪程序的执行时的系统调用

  5. ZT Android4.2关于bluetooth在HAL层的分析(1)

    我的电子杂烩饭 http://blog.sina.com.cn/wuchuchu2012 [订阅][手机订阅] 首页 博文目录 图片 关于我 正文 字体大小:大 中 小 Android4.2关于blu ...

  6. 自学PHP有哪些书籍和教程值得推荐?

    知乎上看到一题主询问:"自学PHP有哪些书籍和教程值得推荐?",互联网深度屌丝秦风给出了不错的答案,希望能够帮助自学PHP的朋友们. 以下仅供参考: 尤其不认可W3school之类 ...

  7. java String 常用方法集合

    String a = "abc";String b = "abc";a==b ;//返回true,因为a,b指向的是同一个地址 String a = new S ...

  8. vue项目出现的错误汇总

    报错一: expected "indent", got "!" 通过vue-cli创建的项目,不需要在webpack.base.conf.js中再手动配置关于c ...

  9. JavaScript的事件对象_滚轮事件

    用户在使用键盘时会触发键盘事件.“DOM2 级事件”最初规定了键盘事件,结果又删除了相应的内容.最终还是使用最初的键盘事件,不过 IE9 已经率先支持“DOM3”级键盘事件. 一.键码 在发生 key ...

  10. JavaScript的DOM_通过元素的class属性操作样式

    使用 style 属性可以设置行内的 CSS 样式,而通过 id 和 class 调用是最常用的方法. <script type="text/javascript"> ...