在 PHP 中管道(Pipeline) 能帮咱们作什么?

前言

看到这个标题,你或许会疑惑:“什么是管道?”;“它有什么用?”;”它有什么优点?“;“它能解决什么”‘下面,就以个人视角来给你解开它。php

什么是管道?

管道,在 PHP 开发中其实咱们不多有听到有人说管道这个词,在 Linux 操做中听到的比较多 “管道操做符 |”,其实这里咱们要探讨的管道和这个也是一个概念,就如名字同样,“管道”,当数据从管道的一头进入后,通过内部的处理,最终再从另外一头出来。
在通过管道的途中,咱们能够对咱们传入的数据或者处理结果进行相应的调整以达到咱们的目的。laravel

它有什么用?

通过上面的介绍,你可能对管道有了必定的认识,可是仅限于理论层面的,至关抽象,甚至于还没法接受这种思想。若是我如今告诉你,若是你使用过 Laravel,其实你早就已经用过它了,你可能会惊讶。是的,在大可能是状况下,你确实已经使用过它了,在 Laravel 中最多见的功能 「中间件」,就是基于管道来实现的。git

中间件

经过使用中间件,能够在对传入的 Request $request 进行操做,甚至修改它的值,咱们也能够调用 $next($request) ,来获取结果后对处理的 Response 添加 Header ,咱们也能够在接到一些特殊请求后直接处理返回,好比处理跨域,大多数时候就是在这里处理的。若是你有兴趣,还能够往下看看github

它能解决什么?

是的,不少时候咱们学习一个新事物的时候,这个事物能给咱们带来什么,一般是咱们最关心的。
试着想一下,你如今有一个电商程序,在最初的时候,你只须要顾客提交商品建立订单、支付,这一切看起来彷佛很简单。编程

// OrderService.php
class OrderService {
    // 建立订单
    public function create(){
        // some code
        return new Order();
    }
    public function pay(Order $order){
        // some code
        $payInterface = new AliPay($order);
        return $payInterface->response();
    }
}

如今我功能基本有了,可是新的需求下来了,商城新加了一个会员卡,一级会员打 9.5 折,二级会员 9 折,三级会员 8.5 折,如今咱们的代码可能成了这样。跨域

class OrderService {
    public function pay(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 AliPay($order);
        return $payInterface->response();
    }
}

这看起来还好,只是折扣计算使得你原来的代码再也不那么“好看”了,若是接下来我告诉你,咱们加入了优惠券系统?部分商品须要打折,你会怎么样?一块儿来看看。数组

// ...
// ...
// ...
class OrderService {
    public function pay(Order $order){
        bcscale(2);
        $amount = '0';
        foreach($order->products as $product){
            // 这里的 discount 咱们就当他是跟下面 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 AliPay($order);
        return $payInterface->response();
    }
}

看起来彷佛也还好,可是若是咱们还有更多这样相似的需求呢?你会发现有点儿不妙,咱们代码方向变了,并且更乱了,更加的很差控制,可能过一段时间,我都已经“不敢认可”,这曾经是本身写的代码了,看起来咱们须要解决这个问题,如今,是时候让文章的主角“管道”出场了。app

Laravel 中的管道

在 Laravel 中,Laravel 已经帮助咱们实现了一个管道 \Illuminate\Pipeline\Pipeline ,咱们只须要根据须要,把数据放入管道,而后使用咱们本身的处理器去处理数据,最后在管道的另一头将数据输出,数据从进入管道后就再也不去关内心面会发生什么,就像水同样,它只须要关心从管道另外一头输出数据,首先咱们要开始简化一下 pay 方法,让他看起来尽可能保持简洁。异步

class OrderService {
    public function pay(Order $order){
        try{
        $order = pipeline('order_service::pay',$order);
        }catch (Exception $e){
            // log..
            // 这里咱们能够记录日志,也能够不记录,直接不处理这个异常,而后由调用者来处理这个异常,
            // 由于这里在关东中可能出现了一些状况须要中止向下传播
            throw $e;
        }
        $payInterface = new AliPay($order);
        return $payInterface->response();
    }
}

如今引入了一个 pipeline 方法可是如今并不存在这个方法,须要去建立它,用来作管道的触发埋点。函数

如今咱们建立了一个管道函数,用来帮助咱们简化调用,以便在其余地方进行复用,这个方法接收2个参数,一个是埋点,一个是要传递的数据

// functions.php
if(!function_exists('pipeline')){
    function pipeline($pipe,$data){
        $pipeline = resolve(Pipeline::class);
        $handlers = config('pipeline.'.$pipe);
        return $pipeline->through($handlers)->send($data)->thenReturn();
    }
}

如今在配置文件中定义一下这个触发点关联的处理器,就像注册事件那样。

// config/pipeline.php
return [
    'order_service::pay'=>[
        // 产品折扣
        ProductDiscount::class,
        // 会员卡折扣
        UserVipDiscount::class,
        // 使用优惠券
        CouponUsing::class,
    ],
];

接着咱们来实现这些不一样的处理器

class ProductDiscount {
    public function handle($order,$next){
        bcscale(2);
        $amount = '0';
        foreach($order->products as $product) {
            // 这里的 discount 咱们就当他是跟下面 VIP 同样的小数那样。
            // 这里彷佛还应该考虑限时折,这里就不展开写了。
            $amount = bcadd($amount,bcmul($product->price,$product->discount));
        }
        $order->amount = $amount;
        return $next($order);
    }
}
class UserVipDiscount {
    public function handle($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);
        return $next($order);
    }
}

如今就对代码实现了一个解耦,同时保持了 pay 方法的洁净,使得其更加单一,可是,这并不完美,为何?

试想一下,管道中任意一个处理器返回数据错了,按照预期,他应该老是接收一个 Order 对象,而且返回也应该是一个 Order 对象,一直这样重复下去,到最终管道的结果也应该是一个 Order 对象,让咱们能够给 AliPay 对象做为构造方法传递进去调用,可是在这里,咱们并无去限制它,你也能够本身来实现这一部分。

它有什么优点?

管道和事件

经过上面笼统的介绍,和简单的应用,你可能会以为,这样看起来,管道不是和事件很类似了么?

都是注册、而后触发、最后获取结果、异常处理。看起来是很类似,尤为是前面的步骤,注册、触发、异常处理,可是获取结果,好比在 Laravel 中,若是一个事件上注册了多个事件
处理器,那在触发事件后,咱们能够在触发函数接收到每一个事件处理器的返回值组成的数组,是的,若是上面的栗子咱们用事件去作,那么就会返回三个 Order,这三个可能并无关系,由于当商品打折后使用会员卡时,咱们使用会员卡前的价格就应该是打折后的了,而事件就不同,事件始终都是使用的 源对象 ,也就是说在咱们后面的步骤中,实际上使用的仍是咱们触发事件时所传递的那个 Order 对象,可是 Pipeline 会顺序、准确的处理 Order 对象,并在最终返回一个对象给咱们。除此以外,还有一个不一样点,多数状况下管道的应用老是同步的,而事件则是能够选择使用异步来进行处理,进一步提高咱们业务处理效率。

因此说,根据不一样的场景来选择适合业务的方案,不但能够优化咱们的代码,还能够提高性能。

他有点儿像 Hook

若是你看到过一些有关 PHP 中和 插件功能实现的文章或者 ThinkPHP 3.2 的代码,你可能会以为,它有点儿像 Hook,
可是若是你梳理完这二者的实现原理,你会发现,其实 「Hooks」 更像「事件」。

更酷的方案 「AOP(面向切面编程)」

经过上面的应用,咱们知道管道能够帮咱们解决这种简单的问题,可是如今还有一种能加 cool 的方案,使用 AOP 中的前置通知或者环绕通知,咱们也能够作到不侵入原有业务的状况下实现这个功能,若是你感兴趣能够去看看 Swoft 中关于 AOP) 章节的介绍。

可是,AOP 也是彻底可以替代管道,只是在这个例子中能够彻底替代,但愿你能明白这一点,管道可与在代码进行中的任意一个阶段埋点,这一点 AOP 是无法作到的。

结束

看完了以后,你是否很好奇,管道是怎么实现的 ?经过这个连接,你能够了解到在 Laravel 中管道的源码,从而来帮助你更加深入的去理解它。

参考资料

相关文章
相关标签/搜索