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. 关于python中的 take no arguments 的解决方法

    针对第四章编写的代码出现的错误做一个总结 Traceback (most recent call last): File "H:\image\chapter4\p81_chongxie.py ...

  2. 关于DateTimeOffset的爱恨情仇

    为什么要上DataTimeOffset?因为DateTime无法解决时区问题,经常需要时间戳转换日期,DateTimeOffset就是应运而生,数据格式储存了和UTC的时区差,C#几乎全几乎全版本支持 ...

  3. 我的强迫症系列之@Builder和建造者模式

    前言 备受争议的Lombok,有的人喜欢它让代码更整洁,有的人不喜欢它,巴拉巴拉一堆原因.在我看来Lombok唯一的缺点可能就是需要安装插件了,但是对于业务开发的项目来说,它的优点远远超过缺点. 我们 ...

  4. TestFlight无法访问怎么办?TF如何链接AppStoreConnect解决教程

    最近苹果商店半天下架近3万款应用的新闻闹的沸沸扬扬,很多被下架应用的运营商转将APP上架了TestFlight内测商店(TF签名上架),但是从7月31日起很多用户就开始反映TestFlight内测应用 ...

  5. MyBatis深入理解参数

    目录 一.快速创建mapper文件 二.parameterType 三.MyBatis 传递参数 1. 一个简单参数(掌握) 一.快速创建mapper文件 由于每个接口都要创建一个对应的mapper文 ...

  6. 用Java制作斗地主

    首先,按照斗地主规则,完成洗牌发牌的动作.如图: 具体规则: 1. 组装54张扑克牌 2. 将54张牌顺序打乱 3. 三个玩家参与游戏,三人交替摸牌,每人17张牌,最后三张留作底牌. 4. 查看三人各 ...

  7. C#LeetCode刷题之#520-检测大写字母(Detect Capital)

    问题 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/3947 访问. 给定一个单词,你需要判断单词的大写使用是否正确. ...

  8. 自己动手编写一个Mybatis插件:Mybatis脱敏插件

    1. 前言 在日常开发中,身份证号.手机号.卡号.客户号等个人信息都需要进行数据脱敏.否则容易造成个人隐私泄露,客户资料泄露,给不法分子可乘之机.但是数据脱敏不是把敏感信息隐藏起来,而是看起来像真的一 ...

  9. 一网打尽 Java 并发模型

    本篇文章我们来探讨一下并发设计模型. 可以使用不同的并发模型来实现并发系统,并发模型说的是系统中的线程如何协作完成并发任务.不同的并发模型以不同的方式拆分任务,线程可以以不同的方式进行通信和协作. 并 ...

  10. EXCEL 中数据 批量 填充进 word 中

    工具:Python3.7 需求描述:将EXCEL中 第二行 数据 填在 word 对应位置上,然后保存为 "姓名+任务.docx"文件. 再将EXCEL中 第三行 数据 填在 wo ...