5

装饰器模式在Laravel中的应用

 3 years ago
source link: https://segmentfault.com/a/1190000040570901
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

什么是装饰器模式

装饰器模式允许向一个现有的对象添加新的功能,同时又不改变其结构。

简单代码实现:

interface Decorate {
    function getInfo();
}

/**
 * Class DecoateA
 * 初始化一个装饰对象
 */
class DecoateA implements Decorate{

    /**
     * 实现接口
     */
    public function getInfo() {
        echo __CLASS__.PHP_EOL;
    }
}

/**
 * Class DecoateB
 * 装饰DecoateA 的装饰类
 */
class DecoateB implements Decorate{

    /**
     * DecoateB constructor.
     * @param Decorate $dec
     * 构造器 传过来其他装饰类
     */
    public function __construct(Decorate $dec) {
        $this->dec = $dec;
    }

    /**
     * 实现接口
     */
    public function getInfo() {
        echo __CLASS__.PHP_EOL;
        $this->dec->getInfo();
    }
}


// 通过不断的创建装饰对象来添加功能
$obj = new DecoateA();// 初始一个对象
$obj = new DecoateB($obj);// 装饰对象

$obj->getInfo();
// DecoateB
// DecoateA

装饰器和Laravel中间件

装饰器对现有对象不断添加功能的思想,与框架中间件的思想一致,框架中间件就是对装饰器模式的一大应用。

一个请求可以视为现有对象,在请求到达控制器之前,需要经过 Laravel 框架的各种中间件,每个中间件都对请求进行不同的“装饰”,当请求到达控制器执行完毕进行响应客户端,一个响应返回途中要经过刚刚来时的经过的中间件,每个中间件都可以对响应进行不同的“装饰”,最后响应到达客户端。这个流程是一个典型的“洋葱模型”。

以一个简短的代码表达 Laravel 中间件原理

interface Middleware
{
    public static function handle(Closure $next);
}

/**
 * 中间件A(可以视为装饰类A)
 */
class MiddlewareA implements Middleware
{
    public static function handle(Closure $next)
    {
        echo __CLASS__." start".PHP_EOL;
        $next();
        echo __CLASS__." end".PHP_EOL;
    }
}

/**
 * 中间件B(可以视为装饰类B)
 */
class MiddlewareB implements Middleware
{
    public static function handle(Closure $next)
    {
        echo __CLASS__." start".PHP_EOL;
        $next();
        echo __CLASS__." end".PHP_EOL;
    }
}

/**
 * 生成闭包函数
 */
function makeClosureFun($carry, $middlerwareClass)
{
    return function () use ($carry, $middlerwareClass) {
        return $middlerwareClass::handle($carry);
    };
}

/**
 * 执行
 */

// Kernel 中的中间件数组
$middlerwareArray = [
    'MiddlewareA',
    'MiddlewareB'
];

$prepare = function () {
    echo "_init_".PHP_EOL;
};

// 组装闭包
$closure = array_reduce($middlerwareArray, 'makeClosureFun', $prepare);
// 执行
call_user_func($closure);
MiddlewareB start
MiddlewareA start
_init_
MiddlewareA end
MiddlewareB end

以上代码输出的结果,正好符合中间件的“洋葱模型”。

这是如何是实现的呢?实现中间件的关键是 array_reduce() + 闭包函数。

array_reduce() 第一个参数是数组,第二个参数是回调函数,第三个参数是处理开始或结束时触发的函数。

array_reduce 会给回调函数传入两个参数,carry 和 item ,分别是上次迭代返回的值和本次迭代的值。

当组装闭包时,局部代码如下

function makeClosureFun($carry, $middlerwareClass)
{
    return function () use ($carry, $middlerwareClass) {
        // 忽略视为黑盒
    };
}

// 组装闭包
$closure = array_reduce($middlerwareArray, 'makeClosureFun', $prepare);

makeClosureFun() 返回了一个 use 两个变量的闭包函数,至于这个闭包函数内部具体做了什么不用关注,将其视为一个黑盒。闭包函数 use 的 这两个参数分别是 array_reduce() 传给闭包的上次迭代返回的值和本次要迭代的值。

当第一次迭代执行 makeClosureFun() 时,返回了一个闭包函数,这个闭包函数存储时实际上时一个闭包类,可以看到 use 的两个参数存到 static 属性下。

object(Closure)#2 (1) {
    ["static"]=>
    array(2) {
    ["carry"]=>
    object(Closure)#1 (0) {
    }
    ["middlerwareClass"]=>
    string(11) "MiddlewareA"
    }
}

第一个迭代返回的闭包函数,继续作为了第二次迭代的 $carry 传入, $middlerwareClass 变为了 $middlerwareArray 数组的第二个元素。如此迭代,到最后得到的 $closure

object(Closure)#3 (1) {
  ["static"]=>
  array(2) {
    ["carry"]=>
    object(Closure)#2 (1) {
      ["static"]=>
      array(2) {
        ["carry"]=>
        object(Closure)#1 (0) {
        }
        ["middlerwareClass"]=>
        string(11) "MiddlewareA"
      }
    }
    ["middlerwareClass"]=>
    string(11) "MiddlewareB"
  }
}

使用 call_user_func() 执行 $closure 变量中的闭包函数。

TODO: PHP 如何根据闭包变量中的结构找到对应函数代码片段?(OR call_user_func如何执行闭包函数?)

第一次执行 use 引入 $carry$middlerwareClass, 执行 $middlerwareClass::handle($carry),此时 $middlerwareClass 值为 MiddlewareB,即执行 MiddlewareB::handle($carry) ,传入的 $carry 值是一个闭包。MiddlewareB类中 handle() 方法在先执行 MiddlewareB start 输出,通过 $next() 执行闭包,即第二次执行。

这次执行 $middlerwareClass::handle($carry),此时 $middlerwareClass 值为 MiddlewareA,即执行 MiddlewareA::handle($carry),输出 MiddlewareA start , 执行闭包 $next(),PHP发现闭包为空,触发 array_reduce() 给设定的 $prepare 闭包,输出 _init_,随后开始回头,输出 MiddlewareA end,再输出MiddlewareB end

主要参考,特别鸣谢此文作者

装饰器模式以及Laravel框架下的中间件应用

Pipeline 管道操作实现请求中间件过滤(最详细讲解)


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK