<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                [TOC] # 簡介 Laravel 框架中有一個非常有趣的功能,就是 HTTP 中間件,我們在定義路由的時候,通過中間件對訪問進行過濾。來自外部的請求首先經過全局中間件,若通過,則會繼續穿過層層路由組所設置的中間件,在到達目的路由,當然,目的路由也可能定義了個中間件,通過后,該路由的處理對象(如控制器),得到的就是一個經過過濾的請求了。 # 開始 本文當然不是討論中間件如何使用,而是其實現的基礎。Laravel 框架中有一個組件叫做 Illuminate\Pipeline,意味 “管道”,我們看看下面這個代碼示例: ~~~ <?php use Illuminate\Pipeline\Pipeline; $pipe1 = function ($poster, Closure $next) { $poster += 1; echo "pipe1: $poster\n"; return $next($poster); }; $pipe2 = function ($poster, Closure $next) { if ($poster > 7) { return $poster; } $poster += 3; echo "pipe2: $poster\n"; return $next($poster); }; $pipe3 = function ($poster, Closure $next) { $result = $next($poster); echo "pipe3: $result\n"; return $result * 2; }; $pipe4 = function ($poster, Closure $next) { $poster += 2; echo "pipe4 : $poster\n"; return $next($poster); }; $pipes = [$pipe1, $pipe2, $pipe3, $pipe4]; function dispatcher($poster, $pipes) { echo "result: " . (new Pipeline)->send($poster)->through($pipes)->then(function ($poster) { echo "received: $poster\n"; return 3; }) . "\n"; } echo "==> action 1:\n"; dispatcher(5, $pipes); echo "==> action 2:\n"; dispatcher(7, $pipes); ~~~ 上述代碼執行結果如下: >==> action 1: pipe1: 6 pipe2: 9 pipe4 : 11 received: 11 pipe3: 3 result: 6 ==> action 2: pipe1: 8 >result: 8 # 流程概覽 Pipeline 組件實現了一個過濾流程: > 原始數據 ---> 【前置管道】 ---> 目標處理邏輯 ---> 【后置管道】 ---> 結果數據 通過這種機制,可以將目標處理邏輯與過濾、認證等機制的代碼分離開來,這樣我們就更容易讓代碼清晰和易于維護。通過前置、后置管道,在其中 “放置” 我們需要過濾的邏輯即可,如上述代碼,雖然只是一個簡單的示例,就已經能夠看得出,整個流程的動向,譬如我們在上面示例中準備了四個過濾組件(中間件): pipe1、pipe2、pipe3、pipe4,其中 1、2、4 是前置,3 為后置。 輸入的原始數據為 5,執行過程首先通過 1 號過濾組件,然后是 2 號,再然后是 4 號,到達目標處理邏輯后,再通過 3 號過濾組件,最終輸出結果。 輸入原始數據為 7,同樣是先經過 1 號過濾組件,隨后是 2 號,不過在 2 號中,直接返回了結果,這意味著過程被攔截,不再繼續向下傳遞數據,至此結束并返回結果。 >Laravel 框架中,原始數據是一個 Request 對象,通過所定義的前置中間件,開發者可在中間件中獲取 Request 的信息,比如用戶的 Session/Cookie 以及 Header 等,驗證數據是否完備等等,不完備或不符合要求的,則被攔截并返回一個響應告知。若能正常通過則繼續傳遞至最終的處理邏輯,如控制器的某個方法或者一個匿名函數。通過這種模式,我們就實現了請求校驗和業務邏輯的分離,而且這樣十分便于開發和維護。 # 實現 前面說這么多,不知道讀者是否已經有一套實現的思路了沒。 >Pipeline 這個組件的功能十分明確,實現這種類似功能的肯定不少,選擇其作為代表分析,原因就是其實現的方式非常簡潔、有力,不但其實現原理如此,面對開發人員,它的調用方式也十分清晰,利用匿名函數使得前置與后置的調用都很直觀,本文分析的重點就在這里。 實現的思路即使有了,在沒有很好地基礎之前,估計也很難去完成。當然很多人愿意去閱讀其代碼,這樣就少走了不少彎路,在這里,我的建議也是這樣。不過,很多人看到源碼也很迷惑,因為中間存在著非常多的回調,只要基礎不夠扎實,就很容易在期間產生諸多困惑。 不過,逐步分析和對基礎知識的補完,就會發現再復雜的框架也不過是零碎的功能有序的構建起來的。 # array_reduce 的妙用 ~~~ public function then(Closure $destination) { $firstSlice = $this->getInitialSlice($destination); $callable = array_reduce( array_reverse($this->pipes), $this->getSlice(), $firstSlice ); return $callable($this->passable); } ~~~ 上面的代碼就是 Pipeline 啟動過程的起點,當然在調用 then 方法之前我們還有必要調用 send 和 through,send 是傳遞初始數據,through 則是傳遞需要通過的中間件構成的數組,沒必要贅述。 then 方法接受一個要求匿名函數的參數,該參數所接受的匿名函數,就是用于整個流程的邏輯處理部分的,數據穿過層層中間件,最終到達這里,所以該匿名函數可接受一個參數,就是經過過濾的數據啦。該方法囊括著所有功能,但是代碼不過幾行,因此肯定有額外的調度過程。 代碼中首先映入眼簾的就是 $this->getInitialSlice() ,該方法顧名思義,創建了一個初始化用的 Slice,這塊我們先不細說,因為隨后就是本文的重點,亦是組件實現的 核心功能 :array_reduce 函數!。 >array_reduce 函數的作用文檔上寫的十分詳細,可至官方中文文檔查閱:http://php.net/manual/zh/function.array-reduce.php >通過查閱文檔,我們可通過示例了解其作用本質就是通過用戶自定義的方式去將一個數組合并成單一的一個值,因此該函數要求三個參數:待合并的數組、用于合并邏輯的回調函數、初始合并的值(亦或者特殊情境下的最終值),用于合并邏輯的回調須接受兩個參數值,分別是上一次處理邏輯處理的結果(第一次不存在處理結果,則默認為空,若設置了 array_reduce 的第三個參數,則以該參數為初始值)和待處理的數組項。 Pipeline 組件恰到好處的使用了它。我們看得到,Pipeline 首先將我們用于處理的中間件數組通過 array_reverse 取相反順序(至于為什么這么做后面你們就知道了),傳遞至 array_reduce 的第一個參數。第三個參數作為 array_reduce 認定的默認處理對象,Pipeline 用的是先前通過 getInitalSlice 獲取到的(實際上是用戶傳進來的目標邏輯處理函數)作為值傳遞。 然后就是本文第二個介紹的重點,array_reduce 所接受的第二個參數,通過調用 $this->getSlice() 獲取的一個匿名函數! # 實現的核心 array_reduce 的第二個參數要求傳遞一個回調函數用于處理數組合并,$this->getSlice() 返回的正是這個處理函數,我相信你們一定看到了 getSlice 返回的值,那么我就將這個匿名函數單獨拿出來: ~~~ function ($stack, $pipe) { return function ($passable) use ($stack, $pipe) { if ($pipe instanceof Closure) { return call_user_func($pipe, $passable, $stack); } // 省略了一部分,該部分是針對中間件 “類” 而不是中間件匿名函數的, // 先前例子中我們用的都是以匿名函數作為數組傳遞進來的,因此只會進入上面那個條件, // 當然 Laravel 框架中,傳遞進來的則基本是中間件對象的類名,這段省略的代碼, // 和上面那個 if 中的本質的區別就是,省略的代碼中包含了中間件類的實例化過程并調用的是 // 其 handle 方法而不是直接調用函數,僅此~~ }; }; ~~~ 我知道大家看到的代碼有很多行,但是實際上就只有一行 return function() { ... };,被執行的也只有它。對于一些初學者,很容易產生一種錯覺:那個返回的 function 會在 return 前執行。既然是錯覺,那就意味著不會被執行,而是作為一個值被返回,可能會被后續某個地方所調用!可能會被后續某個地方所調用!可能會被后續某個地方所調用!這里只是個值!重要的事情說三遍。 > 雖說會被后面所調用,但我們依舊要在這里提一下這個被返回的匿名函數,在這里,它又有著另一個名稱:閉包。閉包是由匿名函數(也成閉包函數)構成的一個整體,和普通的匿名函數有所不同,閉包中一定存在引用了外部數據并在內部操作的情況。 這里需要注意,返回的不僅僅是個匿名函數,更是一個閉包,該閉包中引用了兩個外部值,分別是 array_reduce 提供給第二參數中的回調的兩個參數,即數組合并結果和當前待合并的值。 第一次執行時,$stack 就是我們的目標處理邏輯代碼段,$pipe 則是第一個中間件; 第二次執行時,$stack 是第一次執行所返回的閉包,$pipe 則是第二個中間件,隨后以此類推。 最后一次執行,返回的結果仍舊是一個閉包,該閉包中所引用的外部數據是倒數第二次的執行返回的閉包,$pipe 是最后一個中間件。隨后,該閉包在 then 方法中被調用,傳遞進了我們通過 send 方法傳遞的值。 上面的描述可能異常抽象,我們讓其變得稍微直觀一些,我會將所有遍歷每一次執行帶來的變化體現出來。不過為了方便理解,我需要改一下示例代碼,去掉中間的條件判斷,因為我們現在重點是理解這個流程而不是其功能,新的代碼與執行結果如下: ~~~ <?php use Illuminate\Pipeline\Pipeline; $pipes = [ function ($poster, $callback) { $poster += 1; return $callback($poster); }, function ($poster, $callback) { $result = $callback($poster); return $result - 1; }, function ($poster, $callback) { $poster += 2; return $callback($poster); } ]; echo (new Pipeline)->send(0)->through($pipes)->then(function ($poster) { return $poster; }); // 執行輸出為 2 ~~~ 上述代碼,我們定義了三個中間件,同時我們的目標邏輯代碼并沒做什么特殊的事情,這樣我們就可以專注在執行流程上。下面便于分析,我做了一份偽代碼以及等式方便理解: ~~~ poster = 0 f^0 = f(z)->{ z } // 定義目標處理邏輯 f^1 = f(z, y)->{ f^y( z + 1 ) } // 定義中間件 1 f^2 = f(z, y)->{ result = f^y(z); result - 1 } // 定義中間件 2 f^3 = f(z, y)->{ f^y( z + 2 ) } // 定義中間件 3 f^getSlice = f(y, x)->{ f(z)->{ call( f^x(z, y) ) } } callback = array_reduce([f^3, f^2, f^1], f^getSlice, f^0); callback(poster) >>> 執行上述過程 exec^1: // 第一次進行 reduce,y 是目標邏輯片段,x 是最后一個中間件,被閉包引用, // 閉包則作為合并結果返回,在此定義為 f^a。 y = f^0(z); x = f^3; f^a = f(z)->{ call( f^x(z, y) ) } exec^2: // 第二次進行,y 是上次處理返回的閉包(即 f^a),x 是第二個中間件,再次生成閉包返回。 y = f^a; x = f^2; f^b = f(z)->{ call( f^x(z, y) ) } exec^3: // 第三次也是最后一次合并,同第二次。現在三個數組項被合并, // 合并結果為最后一次合并所返回的閉包。 y = f^b; x = f^1; f^c = f(z)->{ call( f^x(z, y) ) } exec^4: // 該閉包(最后一次合并結果)返回后,被調用,第一個參數為 z = poster = 1,開始執行。 // 該閉包的 z 參數即為 1,其余如 x、y 值見 exec^3。 call( f^c(0) ) = call( f^1(0, f^b) ) exec^5: // 繼續等式替換 call( f^b(0 + 1) ) = call( f^2(0 + 1, f^a) ) exec^6: // 根據上已執行過程返回結果,已執行至中間件 2 的回調,繼續等式替換 result = f^a(0 + 1); result - 1 exec^7: result = call( f^3(0 + 1 , f^0) ); result - 1 exec^8: result = call( f^0(0 + 1 + 2) ); result - 1 exec^9: result = 3; result - 1 // 處理結果 result: 2 ~~~ # 分析 根據偽代碼,和執行過程,我們能了解到先前通過 array_reverse 反序排列的中間件,由于在本文中,此處閉包逆向傳遞下去的特性(因為所引用的外部參數中,是前一執行結果所返回的閉包),實際上依舊是按順序執行的,我們在這里也看到了如何利用該特性,實現前置和后置調用的原理以及攔截的原理。 前置調用時,先處理自上傳遞下來的結果,隨后調用下一個(由中間件構成的)閉包。后置調用時,先調用下一個(有中間件構成的)閉包,里面仍舊可能無數的引用,直到其中的目標處理邏輯,最終返回結果,再處理。 攔截的原理就更簡單了,由于攔截只存在于前置中間件,而前置中間件是先處理,然后調用傳遞進來的閉包并返回其值,而若這個值不是來自于一個閉包調用的結果,就意味著肯定中間不存在調用關系,也就根本不會執行到閉包中的下一個中間件。 # 總結 以上就是整個 Pipeline 以及中間件的實現,我知道很多人依舊十分糾結,內心充滿困惑。我仍舊建議老老實實,從 array_reduce 這個函數的實際功能著手,然后把每一步執行過程,寫下來,慢慢的就明白了。這篇文章不僅僅只是 Laravel 組件的一個講解,更多是從中發現 PHP 的一些基礎概念和知識,要知道在強大的 PHP 框架也是用 PHP 寫出的,本質上仍舊是在一個大的基礎上構建的小世界而已 當然也有些composer包 laravel的 ~~~ illuminate/pipeline ~~~ 還有個 https://github.com/thephpleague/pipeline
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看