1

What is the decorator pattern

The decorator pattern allows adding new functionality to an existing object without changing its structure.

Simple code implementation:

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

Decorators and Laravel middleware

Decorator's idea of continuously adding functions to existing objects is consistent with the idea of framework middleware, which is a major application of the decorator pattern.

A request can be regarded as an existing object. Before the request reaches the controller, it needs to go through various middleware of the Laravel framework. Each middleware "decorates" the request differently, and responds to the client when the request reaches the controller and the execution is completed. At the end, a response must pass through the middleware that just arrived on its way back. Each middleware can "decorate" the response differently, and finally the response reaches the client. This process is a typical "onion model".

Express the Laravel middleware principle in a short code

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);

result:

MiddlewareB start
MiddlewareA start
_init_
MiddlewareA end
MiddlewareB end

The output of the above code exactly conforms to the "onion model" of middleware.

Code analysis

How is this achieved? The key to implementing middleware is array_reduce() + closure function.

array_reduce() The first parameter is an array, the second parameter is a callback function, and the third parameter is a function that is triggered when the processing starts or ends.

Array_reduce will pass two parameters to the callback function, carry and item, which are the value returned from the previous iteration and the value of this iteration, respectively.

Assemble the closure

When assembling the closure, the partial code is as follows

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

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

makeClosureFun() returns a use two-variable closure function. As for what is done inside the closure function, don't pay attention to it, and treat it as a black box. The two parameters of the closure function use are the return value of the last iteration passed to the closure by array_reduce() and the value of the current iteration.

When makeClosureFun() is executed for the first iteration, a closure function is returned. This closure function is actually a closure class when stored. You can see that the two parameters of use are stored under the static attribute.

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

The first iteration of closure function returns, continued as the second iteration of $carry passed, $middlerwareClass become a $middlerwareArray the second element of the array. Iterate in this way, to the final $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"
  }
}

Execution closure

Use call_user_func() to execute the closure function in the $closure variable.

TODO: How does PHP find the corresponding function code fragment based on the structure in the closure variable? (How does OR call_user_func execute the closure function?)

The first execution of use introduces $carry and $middlerwareClass , and executes $middlerwareClass::handle($carry) . At this time, the $middlerwareClass is MiddlewareB, that is, executes MiddlewareB::handle($carry) , and the incoming $carry is a closure. MiddlewareB class handle () method previously performed MiddlewareB start output by $next() performing the closure, i.e. the second performance.

Execute $middlerwareClass::handle($carry) this time. At this time, the $middlerwareClass is MiddlewareA, that is, execute MiddlewareA::handle($carry) , output MiddlewareA start , execute closure $next() , PHP finds that the closure is empty, trigger array_reduce() to set $prepare _init_ , and then output 12987ef3c98747d, and then output the , output MiddlewareA end , then output MiddlewareB end .

Reference article

Main reference, special thanks to the author of this article

decorator mode and middleware application under the Laravel framework

Further reading

Pipeline pipeline operation to achieve request middleware filtering (the most detailed explanation)


Mr_houzi
964 声望22 粉丝