装饰器模式在Laravel中的应用
什么是装饰器模式
装饰器模式允许向一个现有的对象添加新的功能,同时又不改变其结构。
简单代码实现:
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中的应用】
文章图片
以一个简短的代码表达 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 管道操作实现请求中间件过滤(最详细讲解)
推荐阅读
- Docker应用:容器间通信与Mariadb数据库主从复制
- 人生感悟记#环境仪器宋庆国成长记#072
- 标签、语法规范、内联框架、超链接、CSS的编写位置、CSS语法、开发工具、块和内联、常用选择器、后代元素选择器、伪类、伪元素。
- 视频转换器哪种好用()
- NeuVector 会是下一个爆款云原生安全神器吗()
- 操作系统|[译]从内部了解现代浏览器(1)
- 探索免费开源服务器tomcat的魅力
- 机器学习|机器学习 Andrew Ng《Machine Learning》课程笔记1
- 《机器学习实战》高清中文版PDF英文版PDF+源代码下载
- 机器学习一些简单笔记