当前位置: 首页 > 后端技术 > PHP

PHP中Pipeline能为我们做什么?

时间:2023-03-29 17:59:17 PHP

前言看到这个标题,你可能会疑惑:“什么是管道?”;“它是做什么用的?”;“它有什么优势?”;透视为您解锁。什么是管道?管道,其实我们在PHP开发中很少听到管道这个词,经常听到的是“管道运算符|”在Linux操作中。其实我们这里要讨论的管道也是一个概念,就像名字一样,“管道”,当数据从管道的一端进入,经过内部处理,最后从另一端出来.在通过管道的途中,我们可以对传入的数据或处理结果进行相应的调整,以达到我们的目的。这是为了什么?经过上面的介绍,你可能对管道有了一定的了解,但只是停留在理论层面,相当抽象,甚至无法接受这种想法。如果我现在告诉你,如果你用过Laravel,你可能会感到惊讶,你已经用过了。是的,在大多数情况下,你确实使用过它。Laravel中最常见的功能“中间件”是基于管道实现的。中间件通过使用中间件,您可以对传入的Request$request进行操作,甚至可以修改其值。我们也可以调用$next($request)来获取结果,并为处理后的Response添加Header。我们也可以在收到一些特殊的请求后,直接处理返回,比如处理跨域,大部分时候都是在这里处理的。有兴趣的也可以往下看,看看能解决什么问题?是的,很多时候我们学习一个新的东西,这个东西能给我们带来什么,通常是我们最关心的。试想一下,您现在有一个电子商务程序。一开始,你只需要客户提交产品,创建订单,付款,这一切看起来都很简单。//OrderService.phpclassOrderService{//创建订单publicfunctioncreate(){//一些代码returnnewOrder();}publicfunctionpay(Order$order){//一些代码$payInterface=newAliPay($order);返回$payInterface->response();}}现在功能基本有了,但是新的需求下来了,商城新增加了一张会员卡,一级会员15折,二级会员10折,10折三级会员15折,现在我们的代码可能是这样的。classOrderService{publicfunctionpay(Order$order){$vipLevel=(int)Auth::user()->vipLevel();$vipMappings=[0=>1,1=>0.95,2=>0.9,3=>0.85,];//是的,为了保险起见,当值出现问题时,默认为1,即不打折。$order->amount=bcmul($order->amount,$vipMappings[$vipLevel]??1);$payInterface=new支付宝($order);返回$payInterface->response();}}这看起来还行吧,只是折扣计算让你原来的代码不再那么“漂亮”了,如果我接下来告诉你,我们加入了优惠券系统呢?有些产品需要打折,怎么办?让我们来看看。//...//...//...classOrderService{publicfunctionpay(Order$order){bcscale(2);$金额='0';foreach($order->productsas$product){//这里的折扣和下面的VIP是一样的小数处理。//看来这里也要考虑限时优惠,这里就不展开写了。$amount=bcadd($amount,bcmul($product->price,$product->discount));}$order->amount=$amount;$vipLevel=(int)Auth::user()->vipLevel();$vipMappings=[0=>1,1=>0.95,2=>0.9,3=>0.85,];//是的,为了保险起见,当值出现问题时,默认为1,即不打折。$order->amount=bcmul($order->amount,$vipMappings[$vipLevel]??1);$payInterface=new支付宝($order);返回$payInterface->response();}}看起来不错,但是如果我们有更多这样的需求怎么办?你会发现有些事情不太妙。我们的代码方向变了,更乱了,更难控制了。也许过了一段时间,我已经“不敢承认”了。这段代码是我自己写的。看来我们需要解决这个问题了,现在,是时候让文章的主角“烟斗”出来了。Laravel中的Pipeline在Laravel中,Laravel已经帮我们实现了一个pipeline\Illuminate\Pipeline\Pipeline,我们只需要根据需要将数据放入pipeline中,然后使用自己的processor来处理数据,最后在pipeline的另一端输出数据。数据进入管道后,它不再关心里面发生了什么。就像水一样,它只需要关心从管道的另一端输出数据。首先,我们需要简化pay方法,使其看起来尽可能简洁。classOrderService{publicfunctionpay(Order$order){try{$order=pipeline('order_service::pay',$order);}catch(Exception$e){//log..//这里可以记录,也可以不记录,直接不处理这个异常,然后由调用者处理这个异常,//因为可能有一些情况在需要停止向下传播的关东抛出$e;}$payInterface=new支付宝($订单);返回$payInterface->response();}}现在引入了一个pipeline方法,但是这个方法现在不存在,需要创建它作为pipeline的触发点。现在我们已经创建了一个管道函数来帮助我们简化调用,以便它可以在其他地方重用。该方法接收2个参数,一个是埋点,一个是要传递的数据//functions.phpif(!function_exists('pipeline')){functionpipeline($pipe,$data){$pipeline=resolve(管道::类);$handlers=config('pipeline.'.$pipe);返回$pipeline->through($handlers)->send($data)->thenReturn();现在在配置文件中定义与这个触发点关联的处理程序,就像注册一个事件一样。//config/pipeline.phpreturn['order_service::pay'=>[//商品折扣ProductDiscount::class,//会员卡折扣UserVipDiscount::class,//使用优惠券CouponUsing::class,],];然后我们实现这些不同的处理程序classProductDiscount{publicfunctionhandle($order,$next){bcscale(2);$金额='0';foreach($order->productsas$product){//此处我们将折扣视为与下面的VIP相同的小数。//看来这里也要考虑限时优惠,这里就不展开写了。$amount=bcadd($amount,bcmul($product->price,$product->discount));}$order->amount=$amount;返回$next($order);}}classUserVipDiscount{公共函数句柄($order,$next){$vipLevel=(int)Auth::user()->vipLevel();$vipMappings=[0=>1,1=>0.95,2=>0.9,3=>0.85,];//是的,为了保险起见,当值出现问题时,默认为1,即不打折。$order->amount=bcmul($order->amount,$vipMappings[$vipLevel]??1);返回$next($order);}}现在代码解耦了,同时保持了pay方法的简洁,反而显得更加单调,但并不完美,为什么呢?试想一下,管道中的任何处理器都会返回错误的数据。按照预期,它应该总是接收一个Order对象,并返回一个Order对象。如此反复,直到流水线的最终结果也应该是一个Order对象,这样我们就可以传入并调用AliPay对象作为构造方法,不过这里我们不做限定,这部分你也可以自己实现。它的优点是什么?管道和事件通过上面的大致介绍和简单的应用,你可能会觉得,这样一来,管道是不是很像事件呢?都是注册,然后触发,最后得到结果和异常处理。看起来很像,尤其是前面的步骤,注册,触发,异常处理,但是要得到结果,比如在Laravel中,如果在一个事件上注册了多个事件处理器,在事件触发后,我们可以触发函数从每个事件处理程序接收一个返回值数组。是的,如果我们用事件来做上面的栗子,那么会返回三个Order。这三个可能无关紧要,因为产品打折的时候,会用到我们用会员卡的时候,我们用会员卡之前的价格应该是打折的,但是pipeline不一样,pipeline永远是使用的源对象,也就是说,在我们后面的步骤中,我们实际使用的是我们在事件触发时传入的Order对象,但是Pipeline会按顺序准确的处理Order对象,最后返回一个对象给我们。除此之外,还有一个区别。大多数情况下,管道的应用始终是同步的,而事件可以异步处理,进一步提高我们的业务处理效率。所以根据不同的场景选择适合业务的方案,不仅可以优化我们的代码,还可以提升性能。他有点像胡克。如果你看过一些关于PHP中和插件功能实现的文章或者ThinkPHP3.2的代码,你可能会觉得它有点像Hook,但是如果你梳理一下两者的实现原理,你会发现“Hooks”实际上更像是“事件”。一个更酷的解决方案《AOP(Aspect-OrientedProgramming)》通过上面的应用,我们知道管道可以帮助我们解决这个简单的问题,但是现在又多了一个更酷的解决方案,在AOP中使用pre-advice或者surroundNotice,我们也可以在不侵入原有业务的情况下实现这个功能。有兴趣的可以去看看Swoft中AOP章节的介绍。不过AOP也可以完全替代pipeline,只是在这个例子中是可以完全替代的。我希望你能理解这一点。管道可以嵌入代码过程的任何阶段,这是AOP做不到的。看完最后是不是很好奇pipeline是怎么实现的呢?通过此链接,您可以了解Laravel中流水线的源码,以帮助您更深入地了解它。Laravel中的参考管道—管道设计范例|Laravel中国社区framework/Pipeline.phpat7.x·laravel/framework老司机带你实现Laravel的流水线|Laravel中国社区