linusshops/pipeline

一个逐步处理的简单工具。

v1.0.0 2023-07-31 18:55 UTC

This package is auto-updated.

Last update: 2024-08-31 00:33:35 UTC


README

基于Laravel的管道,这个小巨人可以在任何对象上逐步处理。更多文档请参阅https://laravel.net.cn/docs/10.x/helpers#pipeline

目录

  1. 安装
  2. 测试
  3. 概述
  4. 管道可以是类
  5. 管道可以是闭包
  6. 管道可以中止进一步处理
  7. 管道可以将自己移动到管道末尾

安装

此软件包可以通过Composer安装。

composer require linusshops/pipeline

测试

可以使用PHPUnit运行测试。安装后运行:

vendor/bin/phpunit

概述

想象一下,你的系统中发生了一个事件,比如上传了一个CSV文件,并触发了某些操作,

  1. 文件被验证(例如大小、扩展名、MIME类型等)
  2. 文件被处理(例如解析并提取到关系型数据库中)
  3. 文件被归档(例如移动到远程共享中)
  4. 需要发送通知(例如向业务用户和最终用户发送电子邮件)

你可能有一些对象负责这些步骤中的每一个,

  1. 一个$validator
  2. 一个$processor
  3. 一个$archiver
  4. 一个$notifier

很好。在一个传统的面向对象应用中,调用代码可能如下所示,

$validator->validate($file);
$processor->process($file);
$mover->move($file);
$notifier->notify($file);

很好。这没有错。这种逻辑可以存在于任何地方,但在传统的MVC应用中,它可能存在于控制器、模型或这些的辅助类中。

那么,管道是如何做到这一点的呢?

(new Pipeline())
    ->send($file)
    ->through([
        $validator,
        $processor,
        $archiver,
        $notifier
    ])
    ->thenReturn();

每个管道将接收到文件对象,可以自由地对其进行任何操作。完成工作后,它可以调用下一个管道,也可以不调用。让我们看看一些示例。

管道可以是类

class Validate
{
    public function handle(File $file, Closure $next)
    {        
        // ... business logic ...
    
        return $next($file);
    }
}

class Process
{
    public function handle(File $file, Closure $next)
    {        
        // ... business logic ...
    
        return $next($file);
    }
}

class Archive
{
    public function handle(File $file, Closure $next)
    {        
        // ... business logic ...
    
        return $next($file);
    }
}

class Notify
{
    public function handle(File $file, Closure $next)
    {        
        // ... business logic ...
    
        return $next($file);
    }
}

$pipes = [
    new Validate(),
    new Process(),
    new Archive(),
    new Notify()
];

(new Pipeline())
    ->send(new File())   // Start with a file
    ->through($pipes)    // validate, process, archive, notify
    ->thenReturn();

管道可以是闭包

$pipes = [
    // Multiply by 10
    function ($input, $next) {
        // Modify the input
        $input = $input * 10;
        
        // Run the next pipe with the modified input
        return $next($input);
    },

    // Divide by 5
    function ($input, $next) {
        // Modify the input
        $input = $input / 5;
        
        // Run the next pipe with the modified input
        return $next($input);
    },

    // Add 1
    function ($input, $next) {
        // Modify the input
        $input = $input + 1;
        
        // Run the next pipe with the modified input
        return $next($input);
    },
];

$output = (new Pipeline())
    ->send(10)           // Start with 10
    ->through($pipes)    // Multiply by 10, divide by 5, add 1
    ->thenReturn();
    
// Output: 21

管道可以中止进一步处理

$pipes = [
    fn($input, $next) => $next($input . 'A'),
    function ($input, $next) {
        // Abort further processing by returning the current $input.
        // The important part is that we don't call `$next($input)`.
        // We can return anything, false, null, $input etc. as long as it doesn't
        // Run the next pipe with the modified input.
        if ($input === 'A') {
            return $input;
        }

        // The remainder of this, as well as the next pipe,
        // will not execute. 
        $input .= 'B';

        return $next($input);
    },
    fn($input, $next) => $next($input . 'C'),
];

$output = (new Pipeline())
    ->send('')           // Start with an empty string
    ->through($pipes)    // Append A. Immediately stop further processing and return A.
    ->thenReturn();
    
// Output: A

管道可以将自己移动到管道末尾

$pipes = [
    function ($input, $next) {
        // Immediately run the next pipes and get their results.
        $result = $next($input);
        $result .= 'A';

        return $result;
    },
    fn($input, $next) => $next($input . 'B'),
    fn($input, $next) => $next($input . 'C'),
];

$output = (new Pipeline())
    ->send('')           // Start with an empty string
    ->through($pipes)    // The first pipe immediately calls the next pipe, so we move on to B, then C, then finally A is run at the end. 
    ->thenReturn();
    
// Output: BCA