laravel 管道设计模式
laravel中的管道(Pipeline)是什么?
所謂管道(Pipeline)設計模式,就是把數據傳遞給一個任務隊列,由任務隊列按次序依次對數據進行加工處理。在laravel框架中,這里的數據就是http請求,任務隊列包含了一個又一個的中間件。
類比1:以流水線或流水管道作類比,流水線上的產品(http請求),依次經過一個又一個的加工單元(對應一個又一個的中間件)進行處理,最后生成產品(http響應)。
類比2:同樣的,也可以與linux下的管道作類比,
cat helloworld.txt | grep "hello world" | rev | > output.txt
不過,差異的地方是,linux shell管道中任務隊列中的單元是一個又一個的進程。而laravel框架中的Pipeline是運行在一個進程中的一個又一個的程序塊,或者說邏輯片。
laravel中如何使用pipeline?
Laravel 在框架中的很多地方使用了管道設計模式,最常見的就是中間件的實現。
當請求最終到達控制器動作被處理前,會先經過一系列的中間件。每個中間價都有一個獨立的職責,例如,設置 Cookie、判斷是否登錄以及阻止 CSRF 攻擊等等。每個階段都會對請求進行處理,如果請求通過就會被傳遞給下一個處理,不通過就會返回相應的 HTTP 響應。
這種機制使得我們很容易在請求最終到達應用代碼前添加處理操作,當然如果不需要這個處理操作你也可以隨時移除而不影響請求的生命周期。
Pipeline有什么優點?
1. 將復雜的處理流程分解成獨立的子任務,從而方便測試每個子任務;
2. 被分解的子任務可以被不同的處理進程復用,避免代碼冗余。(這里說的不同的處理進程是指,針對不同的http請求,采用不同的子任務組合來處理)
3. 在復雜進程中添加、移除和替換子任務非常輕松,對已存在的進程沒有任何影響。
Pipeline有什么缺點?
1. 雖然每個子任務變得簡單了,但是當你再度嘗試將這些子任務組合成完整進程時有一定復雜性;
2. 你還需要保證獨立子任務測試通過后整體的流程能正常工作,這有一定的不確定性。(因為在管道中流動的是http請求,并且子任務可以修改http請求,這樣就存在前一次的修改內容導致下一個子任務的執行失敗的可能性)
3. 當你看到的都是一個個子任務時,對理解整體流程帶來困難(盲人摸象的故事想必大家很熟悉,正是此理)。
代碼理解
這里只局部分析管道實現的三個文件,它們并沒有組成一個完整的工作流程。想要了解完整流程,還需要研究后面的幾個文件。
分析的文件:
1. laravel/vendor/laravel/framework/src/Illuminate/Contracts/Pipeline/Pipeline.php
2. laravel/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php
3. laravel/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php了解完整流程,還需要看以下文件:
1. laravel/public/index.php
2. laravel/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php
3. laravel/vendor/laravel/framework/src/Illuminate/Foundation/Application.php
下面的部分是對Pipeline接口的定義,定義了send、through、via、then四個方法。(這里不知道via方法具體是干嘛的)
laravelvendor/laravel/framework/src/Illuminate/Contracts/Pipeline/Pipeline.php
1 <?php
2
3 namespace IlluminateContractsPipeline;
4
5 use Closure;
6
7 interface Pipeline
8 {
9 /**
10 * Set the traveler object being sent on the pipeline.
11 *
12 * @param mixed $traveler
13 * @return $this
14 */
15 public function send($traveler);
16
17 /**
18 * Set the stops of the pipeline.
19 *
20 * @param dynamic|array $stops
21 * @return $this
22 */
23 public function through($stops);
24
25 /**
26 * Set the method to call on the stops.
27 *
28 * @param string $method
29 * @return $this
30 */
31 public function via($method);
32
33 /**
34 * Run the pipeline with a final destination callback.
35 *
36 * @param Closure $destination
37 * @return mixed
38 */
39 public function then(Closure $destination);
40 }
View Code
接下來,是對管道的實現.
protected $container; //保存服務容器的實例
protected $passable; //保存傳入的http請求
protected $pipes = []; //保存子任務隊列,子任務可以使閉包函數,也可以使類名與參數名的字符串組合
protected $method = 'handle'; //當子任務是類名+參數的字符串組合時,$method指定在管道處理到該類子任務時,該類子任務用來處理http請求的方法名。$method默認是handle,但是可以通過 via()方法修改
在一次http的請求過程中,以下方法的被調用的過程是:send() -> through() -> then() 。
public function send($passable){} //傳入初始的http請求
public function through($pipes){} //設置管道的子任務隊列
public function via($method){} //設置$method的值
public function then(Closure $destination){} //啟動管道,用設定的子任務隊列去處理http請求
protected function getSlice(){} //本函數返回array_reduce()中所需要的第二個參數,callback函數
protected function getInitialSlice(Closure $destination){} //本函數對應array_reduce()中所需要的第三個參數,初始化值
protected function parsePipeString($pipe){} //針對子任務是類名+參數名的字符串組合,提取類名和參數
由上面可知,整個管道的處理邏輯主要集中在then()方法中。
then()方法中,最難懂的是下面這句話:
1 return call_user_func( 2 array_reduce($pipes, $this->getSlice(), $firstSlice), $this->passable 3 );結合getSlice()方法,array_reduce()的處理過程實際上是,利用了閉包函數的特點,用閉包函數保存了局部作用域中的參數$stack和$pipe,并將保存了局部scope的閉包函數作為對象,壓如由$stack保存的堆棧中,當將整個逆序的子任務隊列的執行函數的閉包函數形式壓入棧中后,再通過call\_user\_func(),傳入$this->passable(即http請求),從棧中依次彈出閉包函數處理請求。
laravelvendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php
1 <?php
2
3 namespace IlluminatePipeline;
4
5 use Closure;
6 use IlluminateContractsContainerContainer;
7 use IlluminateContractsPipelinePipeline as PipelineContract;
8
9 class Pipeline implements PipelineContract
10 {
11 /**
12 * The container implementation.
13 *
14 * @var IlluminateContractsContainerContainer
15 */
16 protected $container;
17
18 /**
19 * The object being passed through the pipeline.
20 *
21 * @var mixed
22 */
23 protected $passable;
24
25 /**
26 * The array of class pipes.
27 *
28 * @var array
29 */
30 protected $pipes = [];
31
32 /**
33 * The method to call on each pipe.
34 *
35 * @var string
36 */
37 protected $method = 'handle';
38
39 /**
40 * Create a new class instance.
41 *
42 * @param IlluminateContractsContainerContainer $container
43 * @return void
44 */
45 public function __construct(Container $container)
46 {
47 $this->container = $container;
48 }
49
50 /**
51 * Set the object being sent through the pipeline.
52 *
53 * @param mixed $passable
54 * @return $this
55 */
56 public function send($passable)
57 {
58 $this->passable = $passable;
59
60 return $this;
61 }
62
63 /**
64 * Set the array of pipes.
65 *
66 * @param array|mixed $pipes
67 * @return $this
68 */
69 public function through($pipes)
70 {
71 $this->pipes = is_array($pipes) ? $pipes : func_get_args();
72
73 return $this;
74 }
75
76 /**
77 * Set the method to call on the pipes.
78 *
79 * @param string $method
80 * @return $this
81 */
82 public function via($method)
83 {
84 $this->method = $method;
85
86 return $this;
87 }
88
89 /**
90 * Run the pipeline with a final destination callback.
91 *
92 * @param Closure $destination
93 * @return mixed
94 */
95 public function then(Closure $destination)
96 {
97 $firstSlice = $this->getInitialSlice($destination);
98
99 $pipes = array_reverse($this->pipes);
100
101 return call_user_func(
102 array_reduce($pipes, $this->getSlice(), $firstSlice), $this->passable
103 );
104 }
105
106 /**
107 * Get a Closure that represents a slice of the application onion.
108 *
109 * @return Closure
110 */
111 protected function getSlice()
112 {
113 return function ($stack, $pipe) {
114 return function ($passable) use ($stack, $pipe) {
115 // If the pipe is an instance of a Closure, we will just call it directly but
116 // otherwise we'll resolve the pipes out of the container and call it with
117 // the appropriate method and arguments, returning the results back out.
118 if ($pipe instanceof Closure) {
119 return call_user_func($pipe, $passable, $stack);
120 } else {
121 list($name, $parameters) = $this->parsePipeString($pipe);
122
123 return call_user_func_array([$this->container->make($name), $this->method],
124 array_merge([$passable, $stack], $parameters));
125 }
126 };
127 };
128 }
129
130 /**
131 * Get the initial slice to begin the stack call.
132 *
133 * @param Closure $destination
134 * @return Closure
135 */
136 protected function getInitialSlice(Closure $destination)
137 {
138 return function ($passable) use ($destination) {
139 return call_user_func($destination, $passable);
140 };
141 }
142
143 /**
144 * Parse full pipe string to get name and parameters.
145 *
146 * @param string $pipe
147 * @return array
148 */
149 protected function parsePipeString($pipe)
150 {
151 list($name, $parameters) = array_pad(explode(':', $pipe, 2), 2, []);
152
153 if (is_string($parameters)) {
154 $parameters = explode(',', $parameters);
155 }
156
157 return [$name, $parameters];
158 }
159 }
View Code
下面是對管道添加了異常處理的實現。
laravelvendor/laravel/framework/src/Illuminate/Routing/Pipeline.php
1 <?php
2
3 namespace IlluminateRouting;
4
5 use Closure;
6 use Throwable;
7 use Exception;
8 use IlluminateHttpRequest;
9 use IlluminateContractsDebugExceptionHandler;
10 use IlluminatePipelinePipeline as BasePipeline;
11 use SymfonyComponentDebugExceptionFatalThrowableError;
12
13 /**
14 * This extended pipeline catches any exceptions that occur during each slice.
15 *
16 * The exceptions are converted to HTTP responses for proper middleware handling.
17 */
18 class Pipeline extends BasePipeline
19 {
20 /**
21 * Get a Closure that represents a slice of the application onion.
22 *
23 * @return Closure
24 */
25 protected function getSlice()
26 {
27 return function ($stack, $pipe) {
28 return function ($passable) use ($stack, $pipe) {
29 try {
30 $slice = parent::getSlice();
31
32 return call_user_func($slice($stack, $pipe), $passable);
33 } catch (Exception $e) {
34 return $this->handleException($passable, $e);
35 } catch (Throwable $e) {
36 return $this->handleException($passable, new FatalThrowableError($e));
37 }
38 };
39 };
40 }
41
42 /**
43 * Get the initial slice to begin the stack call.
44 *
45 * @param Closure $destination
46 * @return Closure
47 */
48 protected function getInitialSlice(Closure $destination)
49 {
50 return function ($passable) use ($destination) {
51 try {
52 return call_user_func($destination, $passable);
53 } catch (Exception $e) {
54 return $this->handleException($passable, $e);
55 } catch (Throwable $e) {
56 return $this->handleException($passable, new FatalThrowableError($e));
57 }
58 };
59 }
60
61 /**
62 * Handle the given exception.
63 *
64 * @param mixed $passable
65 * @param Exception $e
66 * @return mixed
67 *
68 * @throws Exception
69 */
70 protected function handleException($passable, Exception $e)
71 {
72 if (! $this->container->bound(ExceptionHandler::class) || ! $passable instanceof Request) {
73 throw $e;
74 }
75
76 $handler = $this->container->make(ExceptionHandler::class);
77
78 $handler->report($e);
79
80 $response = $handler->render($passable, $e);
81
82 if (method_exists($response, 'withException')) {
83 $response->withException($e);
84 }
85
86 return $response;
87 }
88 }
View Code
參考文獻:
1. Laravel 中管道設計模式的使用 —— 中間件實現原理探究
2. 不依賴于任何框架的管道
總結
以上是生活随笔為你收集整理的laravel 管道设计模式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 联通光猫到底怎么连接路由器联通光猫如何和
- 下一篇: PC台式机如何连接蓝牙耳机音箱台式电脑如