Skip to content

管道模式

Testing Is Documentation

tests/Support/PipelineTest.php

QueryPHP 提供了一个管道模式组件 \Leevel\Pipeline\Pipeline 对象。

QueryPHP 管道模式提供的几个 API 命名参考了 Laravel,底层核心采用迭代器实现。

管道就像流水线,将复杂的问题分解为一个个小的单元,依次传递并处理,前一个单元的处理结果作为第二个单元的输入。

Uses

php
<?php

use Leevel\Di\Container;
use Leevel\Kernel\Utils\Api;
use Leevel\Support\Pipeline;

管道模式基本使用方法

fixture 定义

Tests\Pipeline\First

php
namespace Tests\Support;

class First
{
    public function handle(\Closure $next, $send): void
    {
        $_SERVER['test.first'] = 'i am in first handle and get the send:'.$send;
        $next($send);
    }
}

Tests\Pipeline\Second

php
namespace Tests\Support;

class Second
{
    public function handle(\Closure $next, $send): void
    {
        $_SERVER['test.second'] = 'i am in second handle and get the send:'.$send;
        $next($send);
    }
}
php
public function testPipelineBasic(): void
{
    $result = (new Pipeline(new Container()))
        ->send(['hello world'])
        ->through([First::class, Second::class])
        ->then()
    ;

    self::assertSame('i am in first handle and get the send:hello world', $_SERVER['test.first']);
    self::assertSame('i am in second handle and get the send:hello world', $_SERVER['test.second']);

    unset($_SERVER['test.first'], $_SERVER['test.second']);
}

then 执行管道工序并返回响应结果

php
public function testPipelineWithThen(): void
{
    $thenCallback = static function (\Closure $next, $send): void {
        $_SERVER['test.then'] = 'i am end and get the send:'.$send;
    };

    $result = (new Pipeline(new Container()))
        ->send(['foo bar'])
        ->through([First::class, Second::class])
        ->then($thenCallback)
    ;

    self::assertSame('i am in first handle and get the send:foo bar', $_SERVER['test.first']);
    self::assertSame('i am in second handle and get the send:foo bar', $_SERVER['test.second']);
    self::assertSame('i am end and get the send:foo bar', $_SERVER['test.then']);

    unset($_SERVER['test.first'], $_SERVER['test.second'], $_SERVER['test.then']);
}

管道工序支持返回值

php
public function testPipelineWithReturn(): void
{
    $pipe1 = function (\Closure $next, $send) {
        $result = $next($send);
        $this->assertSame($result, 'return 2');
        $_SERVER['test.1'] = '1 and get the send:'.$send;

        return 'return 1';
    };

    $pipe2 = function (\Closure $next, $send) {
        $result = $next($send);
        $this->assertNull($result);
        $_SERVER['test.2'] = '2 and get the send:'.$send;

        return 'return 2';
    };

    $result = (new Pipeline(new Container()))
        ->send(['return test'])
        ->through([$pipe1, $pipe2])
        ->then()
    ;

    self::assertSame('1 and get the send:return test', $_SERVER['test.1']);
    self::assertSame('2 and get the send:return test', $_SERVER['test.2']);
    self::assertSame('return 1', $result);

    unset($_SERVER['test.1'], $_SERVER['test.2']);
}

then 管道工序支持依赖注入

fixture 定义

Tests\Pipeline\DiConstruct

php
namespace Tests\Support;

class DiConstruct
{
    protected $testClass;

    public function __construct(TestClass $testClass)
    {
        $this->testClass = $testClass;
    }

    public function handle(\Closure $next, $send): void
    {
        $_SERVER['test.DiConstruct'] = 'get class:'.$this->testClass::class;
        $next($send);
    }
}

Tests\Pipeline\TestClass

php
namespace Tests\Support;

class TestClass {}
php
public function testPipelineWithDiConstruct(): void
{
    $result = (new Pipeline(new Container()))
        ->send(['hello world'])
        ->through([DiConstruct::class])
        ->then()
    ;

    self::assertSame('get class:'.TestClass::class, $_SERVER['test.DiConstruct']);

    unset($_SERVER['test.DiConstruct']);
}

管道工序无参数

php
public function testPipelineWithSendNoneParams(): void
{
    $pipe = function (\Closure $next): void {
        $this->assertCount(1, \func_get_args());
    };

    $result = (new Pipeline(new Container()))
        ->through([$pipe])
        ->then()
    ;
}

send 管道工序通过 send 传递参数

php
public function testPipelineWithSendMoreParams(): void
{
    $pipe = function (\Closure $next, $send1, $send2, $send3, $send4): void {
        $this->assertSame($send1, 'hello world');
        $this->assertSame($send2, 'foo');
        $this->assertSame($send3, 'bar');
        $this->assertSame($send4, 'wow');
    };

    $result = (new Pipeline(new Container()))
        ->send(['hello world'])
        ->send(['foo', 'bar', 'wow'])
        ->through([$pipe])
        ->then()
    ;
}

through 设置管道中的执行工序支持多次添加

php
public function testPipelineWithThroughMore(): void
{
    $_SERVER['test.Through.count'] = 0;

    $pipe = static function (\Closure $next): void {
        ++$_SERVER['test.Through.count'];

        $next();
    };

    $result = (new Pipeline(new Container()))
        ->through([$pipe])
        ->through([$pipe, $pipe, $pipe])
        ->through([$pipe, $pipe])
        ->then()
    ;

    self::assertSame(6, $_SERVER['test.Through.count']);

    unset($_SERVER['test.Through.count']);
}

管道工序支持参数传入

fixture 定义

Tests\Pipeline\WithArgs

php
namespace Tests\Support;

class WithArgs
{
    public function handle(\Closure $next, $one, $two): void
    {
        $_SERVER['test.WithArgs'] = [$one, $two];
        $next();
    }
}
php
public function testPipelineWithPipeArgs(): void
{
    $params = ['one', 'two'];

    $result = (new Pipeline(new Container()))
        ->through([WithArgs::class.':'.implode(',', $params)])
        ->then()
    ;

    self::assertSame($params, $_SERVER['test.WithArgs']);

    unset($_SERVER['test.WithArgs']);
}

管道工序支持自定义入口方法

fixture 定义

Tests\Pipeline\WithAtMethod

php
namespace Tests\Support;

class WithAtMethod
{
    public function run(\Closure $next, $send): void
    {
        $_SERVER['test.at.method'] = 'i am in at.method handle and get the send:'.$send;
        $next($send);
    }
}
php
public function testStageWithAtMethod(): void
{
    (new Pipeline(new Container()))
        ->send(['hello world'])
        ->through([WithAtMethod::class.'@run'])
        ->then()
    ;

    self::assertSame('i am in at.method handle and get the send:hello world', $_SERVER['test.at.method']);

    unset($_SERVER['test.at.method']);
}