Laravel Pipeline原理及使用

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的优雅吧!

// IlluminateFoundationHttpKernel::sendRequestThroughRouter方法
protected function sendRequestThroughRouter($request)
{
    $this->app->instance('request', $request);
    Facade::clearResolvedInstance('request');

    $this->bootstrap();
	
    // 这段代码真的非常漂亮,实现流接口,面向对象,且人类可读,我们一起结合生活实际感受下
    // new Pipeline 首先获得Pipeline实例    对应的实际场景:找到一个管道
    // send方法 从管道的一头送入经过管道各个节点处理的数据(laravel 6 中真的可以是任何数据)    对应的实际场景:从管道一头送入包含杂质的原料(包含三角形,方形的杂质和我们真正需要的圆形原料)
    // through方法 $this->middleware就是 AppHttpKernel中的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 AppContracts;

use Closure;

interface MyPipeline
{
    public function myViaFunc($raw, Closure $next);
}

2 创建管道中间件
<?php

namespace AppPipes;

use Closure;
use AppContractsMyPipeline;

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(IlluminatePipelinePipeline::class)
    $barString = (new IlluminatePipelinePipeline(app('app')))
        ->via('myViaFunc')
        ->send('some foo string')
        ->through([AppPipesUcFilter::class])
        ->thenReturn();
    dump($barString); // Some foo string
});

Route::get('pipes', function () {
    $barString = app(IlluminatePipelinePipeline::class)
        ->via('myViaFunc')
        ->send('some foo string')
        ->through([AppPipesUcFilter::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(IlluminatePipelinePipeline::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系列的完结了,返回响应给用户。

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

原文地址:https://www.cnblogs.com/alwayslinger/p/13504286.html