Laravel Pipeline原理及使用

开发中可能遇到非常冗长的逻辑,以至于我们想将针对性逻辑拆分出来,但是又拿不准该如何拆分才能实现较高的扩展性并保证较高的维护性,或者说不知道如何优雅的将待处理的数据在多个逻辑中传递,那么面向切面编程(AOP)可能会帮助到你。本文讲解laravel中一个AOP的良好实现Pipeline。 不了解array_reduce的请先查看手册

直接上简易代码

<?php

interface MyFilter
{
public static function handle(array $request, callable $next);
} class FilterScum implements MyFilter
{
public static function handle($request, $next)
{
// 前置中间件一
echo '前置中间件一执行', PHP_EOL;
// some logic inc
array_push($request, 'handled by scum filter');
$next($request);
}
} class FilterPieces implements MyFilter
{
public static function handle($request, $next)
{
// 前置中间件二
echo '前置中间件二执行', PHP_EOL;
array_push($request, 'handled by piece filter');
$next($request);
}
} class FilterDot implements MyFilter
{
public static function handle($request, $next)
{
$request = $next($request);
echo '后置中间件一执行', PHP_EOL;
array_push($request, 'handled by dot filter');
var_dump($request);
}
} // 主逻辑闭包
$mainLogic = function ($request) {
echo 'echo from main logic', PHP_EOL;
array_push($request, 'through main logic');
var_dump($request);
echo 'main logic processed', PHP_EOL;
return $request;
}; $filters = [
FilterScum::class,
FilterPieces::class,
FilterDot::class
]; $filters = array_reverse($filters); $callableOnion = array_reduce($filters, function ($destination, $filter) {
return function ($request) use ($destination, $filter) {
return $filter::handle($request, $destination);
};
}, $mainLogic); // $callableOnion(['got no filtered yet']); call_user_func($callableOnion, ['got no filtered yet']); // 执行文件 输出如下
前置中间件一执行
前置中间件二执行
echo from main logic
array(4) {
[0]=>
string(19) "got no filtered yet"
[1]=>
string(22) "handled by scum filter"
[2]=>
string(23) "handled by piece filter"
[3]=>
string(18) "through main logic"
}
main logic processed
后置中间件一执行
array(5) {
[0]=>
string(19) "got no filtered yet"
[1]=>
string(22) "handled by scum filter"
[2]=>
string(23) "handled by piece filter"
[3]=>
string(18) "through main logic"
[4]=>
string(21) "handled by dot filter"
}

callableOnion产生的讲解(伪代码)

执行array_reduce

step1
return function () { FilterDot::handle($mainLogic) }; step2
return function () {
FilterPieces::handle (
function () { Filter::handle($mainLogic) }
)
}; step3 将他移动到FilterDot类中吧 可能会帮助大家理解
return function () {
FilterScum::handle(){
function () {
FilterPieces::handle (
function () { Filter::handle($mainLogic()) }
)
}
}; 当调用step3生成的最终的闭包的时候,先执行了FilterScum的handle方法,其中echo了字符串,向数组中追加了元素,最后调用了传递的闭包,
即在step2生成的闭包,也就是执行了FilterPieces的handle方法,其中echo了字符串,向数组中追加了元素,最后调用了传递的闭包
即在step1生成的闭包, 也就是执行了FilterDot的handle方法,只不过他先调用了主逻辑闭包,后执行了自身的逻辑 希望看到这里大家能够明白为什么要进行一次array_reverse 以上代码中的filter就是主逻辑中拆分出去的切面,将庞大的逻辑拆分成小而针对性强的逻辑,并通过调用闭包的方式,传递各个切面处理后的数据到另外的切面,从而实现庞大的主逻辑,这样可以实现主逻辑的拆分、扩展,同时保证代码的可维护性。

使用过laravel中间件的道友可能会发现和上面的代码非常类似,没错laravel中的中间件就是面向切面编程的良好实现,那么laravel又是如何确定一个请求需要走过哪些中间件,并且最终调用哪段代码逻辑,并生成响应的呢?请出我们本篇的主角 Pipeline,让我们再次感受laravel的优雅吧!

// Illuminate\Foundation\Http\Kernel::sendRequestThroughRouter方法
protected function sendRequestThroughRouter($request)
{
$this->app->instance('request', $request);
Facade::clearResolvedInstance('request'); $this->bootstrap(); // 这段代码真的非常漂亮,实现流接口,面向对象,且人类可读,我们一起结合生活实际感受下
// new Pipeline 首先获得Pipeline实例 对应的实际场景:找到一个管道
// send方法 从管道的一头送入经过管道各个节点处理的数据(laravel 6 中真的可以是任何数据) 对应的实际场景:从管道一头送入包含杂质的原料(包含三角形,方形的杂质和我们真正需要的圆形原料)
// through方法 $this->middleware就是 App\Http\Kernel中的middleware全局中间件数组,表示请求要先经过这些中间件的过滤,若中间件中返回false,则管道停止前进,不会进行路由匹配 对应的实际场景:在管道的各个节点上加上圆形的筛板,只有圆形的原料可以通过,杂质都被剔除掉了
// then方法,经过了前面中间件的过滤,然后进行路由匹配,执行实际的业务逻辑 对应的实际场景:得到了圆形的原材料,生产产品交付用户
return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
} // 跳转到send方法
public function send($passable)
{
// 要通过管道依次传递到不同切面(中间件)的任何东西
$this->passable = $passable; return $this;
} // 跳转到through方法
public function through($pipes)
{
// 传递的数据要经过的切面(中间件)
$this->pipes = is_array($pipes) ? $pipes : func_get_args(); return $this;
} // 执行最终的逻辑 产生响应返回给用户
// 结合文章开头的简易代码
public function then(Closure $destination)
{
// 生成一条指向目的地,且搭建好了各个筛板(中间件)的管道
$pipeline = array_reduce(
// 跳转到carry方法 其实就是文章开头array_reduce的第二个闭包
array_reverse($this->pipes()), $this->carry(), $this-
// prepareDestination 将最终要执行的方法 分装成闭包
>prepareDestination($destination)
); // 向管道中投递要处理的数据,生成响应并返回
// 就是本文开头的 call_user_func($callableOnion, ['got no filtered yet']);
return $pipeline($this->passable);
} // 返回供array_reduce迭代使用的闭包
protected function carry()
{
return function ($stack, $pipe) {
return function ($passable) use ($stack, $pipe) {
try {
if (is_callable($pipe)) {
// 本篇之说明最简单的$pipe是个闭包的情况,其他分支会在后面的使用示例中展示
// 迭代的将闭包传递闭包中,并返回
return $pipe($passable, $stack);
} elseif (! is_object($pipe)) {
// dump($pipe);
[$name, $parameters] = $this->parsePipeString($pipe);
$pipe = $this->getContainer()->make($name);
$parameters = array_merge([$passable, $stack], $parameters);
} else {
$parameters = [$passable, $stack];
} $carry = method_exists($pipe, $this->method)
? $pipe->{$this->method}(...$parameters)
: $pipe(...$parameters); return $this->handleCarry($carry);
} catch (Exception $e) {
return $this->handleException($passable, $e);
} catch (Throwable $e) {
return $this->handleException($passable, new FatalThrowableError($e));
}
};
};
}

好了,理论讲解就到这里吧,今天的文章只要搞懂array_reduce方法就差不多能够理解了,下面讲解laravel pipeline的使用 毫无疑问laravel依然为我们准备好了相应的服务。

使用

我们实现一个简单的字符串过滤转换管道

1 创建契约
<?php namespace App\Contracts; use Closure; interface MyPipeline
{
public function myViaFunc($raw, Closure $next);
} 2 创建管道中间件
<?php namespace App\Pipes; use Closure;
use App\Contracts\MyPipeline; class UcFilter implements MyPipeline
{
public function myViaFunc($string, Closure $next)
{
$string = ucfirst($string);
return $next($string);
}
} 3 使用管道
// 其中的via thenReturn方法非常简单,请自行查看
Route::get('pipe', function () {
// $barString = app(\Illuminate\Pipeline\Pipeline::class)
$barString = (new \Illuminate\Pipeline\Pipeline(app('app')))
->via('myViaFunc')
->send('some foo string')
->through([App\Pipes\UcFilter::class])
->thenReturn();
dump($barString); // Some foo string
}); Route::get('pipes', function () {
$barString = app(\Illuminate\Pipeline\Pipeline::class)
->via('myViaFunc')
->send('some foo string')
->through([App\Pipes\UcFilter::class])
->then(function ($raw) {
dump($raw); // Some foo string
return substr($raw, 2);
});
dump($barString); // me foo string
});
// thenReturn和then方法的区别,then方法显示的指定了管道的最后一站而已,也就是说产品生产完交付给用户前的最后一站。如果你查阅源码可以发现thenReturn方法就是调用的then方法。希望你是自己发现的。 // 随便折磨一下管道
Route::get('pipess', function () {
$barString = app(\Illuminate\Pipeline\Pipeline::class)
->via('myViaFunc')
->send('some foo string')
->through(
// 你甚至可以这样折磨管道 完全因为laravel pipeline的强大
// 对应carry方法中的各种分支 当然前提是能够通过容器进行解析的类
function ($passable, $next) {
return $next(ucfirst($passable));
},
SomeFilterClass::class,
new SomeClass()
)
->thenReturn();
dump($barString);
}); // 在实际的应用中可能这样使用更有意义些
public function create(Request $request, Pipeline $pipeline)
{
$something = $pipeline->send($request->someField)
->through([
过滤字符串类1::class,
过滤字符串类2::class,
过滤字符串类3::class
...
])->thenReturn();
SomeModel::doSomeShit([]);
...
}

通过上面的不恰当例子,希望能够帮助大家认识管道的使用情景。

不出意外下篇就是laravel系列的完结了,返回响应给用户。

发现错误,还望指教,感谢!!!

Laravel Pipeline原理及使用的更多相关文章

  1. Laravel Facade原理及使用

    Laravel Facade原理及使用 laravel过于庞大,加之笔者水平有限,所以后面的源码解读会按模块功能介绍,希望能帮大家稍微捋顺下思路,即使能够帮助大家回顾几个函数也好.如发现错误,还望指正 ...

  2. drone的pipeline原理与代码分析

    最近的一个项目,需要实现一个工作任务流(task pipeline),基于之前CICD的经验,jenkins pipeline和drone的pipeline进入候选. drone是基于go的cicd解 ...

  3. Redis Pipeline原理分析

    转载请注明出处:http://www.cnblogs.com/jabnih/ 1. 基本原理 1.1 为什么会出现Pipeline Redis本身是基于Request/Response协议的,正常情况 ...

  4. Laravel 认证原理及完全自定义认证

    Laravel 默认的 auth 功能已经是很全面了,但是我们也经常会碰到一些需要自定义的一些情况,比如验证的字段和默认的不匹配,比如需要能够同时满足 user name 和 email 认证等等.如 ...

  5. Laravel底层原理系列

    Laravel 从学徒到工匠精校版 地址:https://laravelacademy.org/laravel-from-appreciate-to-artisan Advanced Applicat ...

  6. 1.pipeline原理

    redis基本语法:https://www.cnblogs.com/xiaonq/p/7919111.html redis四篇:https://www.cnblogs.com/xiaonq/categ ...

  7. LARAVEL 路由原理分析

    <?php class App {    protected $routes = [];    protected $responseStatus = '200 OK';    protecte ...

  8. 2016 版 Laravel 系列入门教程(二)【最适合中国人的 Laravel 教程】

    本教程示例代码见: https://github.com/johnlui/Learn-Laravel-5 在任何地方卡住,最快的办法就是去看示例代码. 本篇文章中,我将跟宝宝们一起学习 Laravel ...

  9. Laravel 系列入门教程(二)【最适合中国人的 Laravel 教程】

    本篇文章中,我将跟大家一起体验 Laravel 框架最重要的部分——路由系统. 如果你读过 2015 版的教程,你会发现那篇文章里大书特书的 Auth 系统构建已经被 Laravel 捎带手给解决了. ...

随机推荐

  1. luogu P4884 多少个1?

    LINK:多少个1? 题目要求:\(\sum_{i=0}^{n-1}10^i \equiv k \mod m\) 最小的n. 看起来很难求的样子 这个同余式 看起来只能暴力枚举. 不过既然是同余 我们 ...

  2. PHP开发者该知道的多进程消费队列

    引言 最近开发一个小功能,用到了队列mcq,启动一个进程消费队列数据,后边发现一个进程处理不过来了,又加了一个进程,过了段时间又处理不过来了… 这种方式每次都要修改crontab,如果进程挂掉了,不会 ...

  3. [转]Nginx介绍-反向代理、负载均衡

    原文:https://www.cnblogs.com/wcwnina/p/8728391.html 作者:失恋的蔷薇 1. Nginx的产生 没有听过Nginx?那么一定听过它的"同行&qu ...

  4. 【BZOJ4631】踩气球 题解(线段树)

    题目链接 ---------------------- 题目大意:给定一个长度为$n$的序列${a_i}$.现在有$m$个区间$[l_i,r_i]$和$q$个操作,每次选取一个$x$使得$a_x--$ ...

  5. Android 菜单的使用

    有时间就随笔记录自己遇到的问题和所学的知识哈. 这是对本牛崽知识的提升也可以给其他牛牛们来点鸡汤和开胃菜. 菜单Menu的创建 首先menu是属于布局的嘛,所以嘞,咱们得在res(也就是布局资源)创建 ...

  6. QString字符串的查找与截取实例

    QString是Qt中封装的字符串类,相对于标准库里的string,使用方法有些不同,个人感觉使用qt习惯后,感觉QString更好用,下面的代码主要是针对QString的字符查找.截取做的测试: # ...

  7. javaweb 测试

    题目要求: 1登录账号:要求由6到12位字母.数字.下划线组成,只有字母可以开头:(1分) 2登录密码:要求显示“• ”或“*”表示输入位数,密码要求八位以上字母.数字组成.(1分) 3性别:要求用单 ...

  8. Xlua中LuaBehaviour的实现

    简介   在基于lua进行热更新的项目中,我们通常会通过luaBehaviour来让lua文件模拟MonoBehaviour,可以让lua文件拥有一些MonoBehaviour的生命周期,如Enabl ...

  9. DB2 分组查询语句ROW_NUMBER() OVER() (转载)

    说起 DB2 在线分析处理,可以用很好很强大来形容.这项功能特别适用于各种统计查询,这些查询用通常的SQL很难实现,或者根本就无发实现.首先,我们从一个简单的例子开始,来一步一步揭开它神秘的面纱,请看 ...

  10. Django-model查询[为空、由某字符串开头、由某字符串结尾、包含某字符串],__isnull、__starswith、__endswith、__contains

    使用属性+__isnull就可以判断此字段为空 a = DatasClass.objects.filter(name__isnull=True) 使用属性+__startswith可以判断属性由某字符 ...