水管太長,只要有一處破了,就會漏水了,並且不利於複雜環境彎曲轉折使用。因此咱們都會把水管分紅很短的一節一節管道,而後最大化的讓管道大小做用不一樣,因地制宜,組裝在一塊兒,知足各類各樣的不一樣需求。javascript
由此得出 Pipeline 的設計模式,就是將複雜冗長的流程 (processes) 截成各個小流程,小任務。每一個最小量化的任務就能夠複用,經過組裝不一樣的小任務,構成複雜多樣的流程 (processes)。php
最後將「輸入」引入管道,根據每一個小任務對輸入進行操做 (加工、過濾),最後輸出知足須要的結果。css
今天主要學習學習「Pipeline」,順便推薦一個 PHP 插件:league/pipeline
。html
第一次知道「pipe」的概念,來自 gulp
的使用。java
gulp
是基於 NodeJS
的自動任務運行器,她能自動化地完成Javascript
、sass
、less
等文件的測試、檢查、合併、壓縮、格式化、瀏覽器自動刷新、部署文件生成,並監聽文件在改動後重復指定的這些步驟。在實現上,她借鑑了 Unix
操做系統的管道 (pipe) 思想,前一級的輸出,直接變成後一級的輸入,使得在操做上很是簡單。laravel
var gulp = require('gulp');
var less = require('gulp-less');
var minifyCSS = require('gulp-csso');
var concat = require('gulp-concat');
var sourcemaps = require('gulp-sourcemaps');
gulp.task('css', function(){
return gulp.src('client/templates/*.less')
.pipe(less())
.pipe(minifyCSS())
.pipe(gulp.dest('build/css'))
});
gulp.task('js', function(){
return gulp.src('client/javascript/*.js')
.pipe(sourcemaps.init())
.pipe(concat('app.min.js'))
.pipe(sourcemaps.write())
.pipe(gulp.dest('build/js'))
});
gulp.task('default', [ 'html', 'css', 'js' ]);
複製代碼
上面的兩個 task
主要是將 less
、全部 js
文件進行解析、壓縮、輸出等流程操做,而後存到對應的文件夾下;每一步操做的輸出就是下一步操做的輸入,猶如管道的流水通常。編程
Laravel 框架中的中間件,就是利用 Illuminate\Pipeline
來實現的,原本想寫寫我對 「Laravel 中間件」源碼的解讀,但發現網上已經有不少帖子都有表述了,因此本文就簡單說說如何使用 Illuminate\Pipeline
。gulp
寫個 demo設計模式
public function demo(Request $request) {
$pipe1 = function ($payload, Closure $next) {
$payload = $payload + 1;
return $next($payload);
};
$pipe2 = function ($payload, Closure $next) {
$payload = $payload * 3;
return $next($payload);
};
$data = $request->input('data', 0);
$pipeline = new Pipeline();
return $pipeline
->send($data)
->through([$pipe1, $pipe2])
->then(function ($data) {
return $data;
});
}
複製代碼
對於該源碼的分析,能夠推薦看這篇文章,分析的挺透徹了:數組
Laravel Pipeline 組件的實現 www.insp.top/article/rea…
上面對 gulp
和 Illuminate\Pipeline
的簡單使用,只是告訴咱們「Pipeline」應用比較普遍。若是讓咱們本身也寫一個相似的插件出來呢,我想應該也不是很難。
下面我拿 League\Pipeline
插件來扒一扒它的源代碼,看如何實現的。
簡述
This package provides a plug and play implementation of the Pipeline Pattern. It’s an architectural pattern which encapsulates sequential processes. When used, it allows you to mix and match operation, and pipelines, to create new execution chains. The pipeline pattern is often compared to a production line, where each stage performs a certain operation on a given payload/subject. Stages can act on, manipulate, decorate, or even replace the payload.
If you find yourself passing results from one function to another to complete a series of tasks on a given subject, you might want to convert it into a pipeline.
安裝插件
composer require league/pipeline
複製代碼
寫個 demo
use League\Pipeline\Pipeline;
// 建立兩個閉包函數
$pipe1 = function ($payload) {
return $payload + 1;
};
$pipe2 = function ($payload) {
return $payload * 3;
};
$route->map(
'GET',
'/demo',
function (ServerRequestInterface $request, ResponseInterface $response ) use ($service, $pipe1, $pipe2) {
$params = $request->getQueryParams();
// 正常使用
$pipeline1 = (new Pipeline)
->pipe($pipe1)
->pipe($pipe2);
$callback1 = $pipeline1->process($params['data']);
$response->getBody()->write("<h1>正常使用</h1>");
$response->getBody()->write("<p>結果:$callback1</p>");
// 使用魔術方法
$pipeline2 = (new Pipeline())
->pipe($pipe1)
->pipe($pipe2);
$callback2 = $pipeline2($params['data']);
$response->getBody()->write("<h1>使用魔術方法</h1>");
$response->getBody()->write("<p>結果:$callback2</p>");
// 使用 Builder
$builder = new PipelineBuilder();
$pipeline3 = $builder
->add($pipe1)
->add($pipe2)
->build();
$callback3 = $pipeline3($params['data']);
$response->getBody()->write("<h1>使用 Builder</h1>");
$response->getBody()->write("<p>結果:$callback3</p>");
return $response;
}
);
複製代碼
運行結果
解讀源代碼
整個插件就這幾個文件:
PipelineInterface
<?php
declare(strict_types=1);
namespace League\Pipeline;
interface PipelineInterface extends StageInterface {
/** * Create a new pipeline with an appended stage. * * @return static */
public function pipe(callable $operation): PipelineInterface;
}
interface StageInterface {
/** * Process the payload. * * @param mixed $payload * * @return mixed */
public function __invoke($payload);
}
複製代碼
該接口主要是利用鏈式編程的思想,不斷添加管道「pipe」,而後增長一個魔術方法,來讓傳入的參數運轉起來。
先看看這個魔術方法的做用:
mixed __invoke ([ $... ] )
當嘗試以調用函數的方式調用一個對象時,__invoke() 方法會被自動調用。參考來自:php.net/manual/zh/l… 如:
<?php
class CallableClass {
function __invoke($x) {
var_dump($x);
}
}
$obj = new CallableClass;
$obj(5);
var_dump(is_callable($obj));
?>
複製代碼
返回結果:
int(5)
bool(true)
複製代碼
Pipeline
<?php
declare(strict_types=1);
namespace League\Pipeline;
class Pipeline implements PipelineInterface {
/** * @var callable[] */
private $stages = [];
/** * @var ProcessorInterface */
private $processor;
public function __construct(ProcessorInterface $processor = null, callable ...$stages) {
$this->processor = $processor ?? new FingersCrossedProcessor;
$this->stages = $stages;
}
public function pipe(callable $stage): PipelineInterface {
$pipeline = clone $this;
$pipeline->stages[] = $stage;
return $pipeline;
}
public function process($payload) {
return $this->processor->process($payload, ...$this->stages);
}
public function __invoke($payload) {
return $this->process($payload);
}
}
複製代碼
其中核心類 Pipeline
的做用主要就是兩個:
Processor
接好各類管道後,那就要「引水入渠」了。該插件提供了兩個基礎執行類,比較簡單,直接看代碼就能懂。
// 按照 $stages 數組順利,遍歷執行管道方法,再將結果傳入下一個管道,讓「水」一層層「流動」起來
class FingersCrossedProcessor implements ProcessorInterface {
public function process($payload, callable ...$stages) {
foreach ($stages as $stage) {
$payload = $stage($payload);
}
return $payload;
}
}
// 增長一個額外的「過濾網」,通過每一個管道後的結果,都須要 check,一旦知足則終止,直接輸出結果。
class InterruptibleProcessor implements ProcessorInterface {
/** * @var callable */
private $check;
public function __construct(callable $check) {
$this->check = $check;
}
public function process($payload, callable ...$stages) {
$check = $this->check;
foreach ($stages as $stage) {
$payload = $stage($payload);
if (true !== $check($payload)) {
return $payload;
}
}
return $payload;
}
}
interface ProcessorInterface {
/** * Process the payload using multiple stages. * * @param mixed $payload * * @return mixed */
public function process($payload, callable ...$stages);
}
複製代碼
咱們徹底也能夠利用該接口,實現咱們的方法來組裝管道和「過濾網」。
PipelineBuilder
最後提供了一個 Builder,這個也很好理解:
class PipelineBuilder implements PipelineBuilderInterface {
/** * @var callable[] */
private $stages = [];
/** * @return self */
public function add(callable $stage): PipelineBuilderInterface {
$this->stages[] = $stage;
return $this;
}
public function build(ProcessorInterface $processor = null): PipelineInterface {
return new Pipeline($processor, ...$this->stages);
}
}
interface PipelineBuilderInterface {
/** * Add an stage. * * @return self */
public function add(callable $stage): PipelineBuilderInterface;
/** * Build a new Pipeline object. */
public function build(ProcessorInterface $processor = null): PipelineInterface;
}
複製代碼
不管是對不一樣技術的橫向理解,仍是基於 Laravel 或者某些開源插件,咱們都能學習到技術之上的通用原理和方法。再將這些原理和方法副作用於咱們的實際代碼開發中。
最近閒來沒事,本身參考 Laravel 去寫個簡易框架,也將League\Pipeline
引入到框架中使用。
「未完待續」