PHP 框架中間件實現

0x00 前言

中間件是不少 PHP 框架都提供的功能,在初次認識它的時候我感到驚訝和興奮。由於它的做用太強大了,在沒有中間件以前咱們不得不將權限驗證和一些公共操做都寫在控制器方法裏,而後控制器就會變得很臃腫,下降了可讀性和可維護性。但有了中間件咱們就能夠這些操做都寫在中間件裏,而後經過使用不一樣的中間件組合不只可以實現需求還下降了代碼的耦合度。既然中間件百般好,那它究竟是如何實現的呢?在閱讀 LaravelSlim 的源碼過程當中(一個讓人感受很費勁,很折磨,以爲本身很菜的受虐過程/(ㄒoㄒ)/~~),我發現其重點就是要將多箇中間件閉包(有些框架中間件並非經過閉包實現但都屬於 callable 的範疇,爲了行文方便統稱爲閉包)經過 array_reduce 或循環的方式將其打包成爲一個閉包的過程。php

0x01 預熱

一說道中間件每每就會讓人聯想到這幅圖html

middleware

看起來很神奇很嚇人的樣子可是仔細觀察一下,其實這個流程其實像不像函數的嵌套調用 Middleware2(Middleware1(App())) 呢?不過這樣嵌套調用顯然是錯的,由於 PHP 先執行了 App() 而不是 Middleware2 可是若是比做嵌套的閉包呢?下面給出示例代碼:數據庫

$allMiddleware = function () {
    echo 'start middleware2' . PHP_EOL;
  
    (function () {
        echo 'start middleware1' . PHP_EOL;
        // app
        (function () {
            echo 'app' . PHP_EOL;
        })();
        // end app
        echo 'end middleware1' . PHP_EOL;
    })();
  
    echo 'end middleware2' . PHP_EOL;
};
$allMiddleware();

// 輸出
// start middleware2
// start middleware1
// app
// end middleware1
// end middleware2
複製代碼

0x02 思考

嘿,上面的代碼結果和預期同樣,但這代碼彷佛有點簡(ruo)陋(zhi)。可是其實上面代碼中的 $allMiddleware 就是中間件組合以後的結果,因此咱們已經摸到門檻了,如今請思考如何將下列中間件閉包自動組合?數組

// 數據庫中間件
$db = function (Closure $next) {
    echo '成功創建數據庫鏈接' . PHP_EOL;
    $next();
    echo '成功關閉數據庫鏈接' . PHP_EOL;
};
// 點贊中間件
$like = function (Closure $next) {
    echo '點贊+1' . PHP_EOL;
    $next();
    echo '點贊+2' . PHP_EOL;
};
// 內容閉包
$app = function () {
    echo '文章內容' . PHP_EOL;
};
複製代碼

多了個參數 $next 是什麼鬼? $next 應該理解爲「本中間件後全部中間件閉包函數打包成的一個閉包」,就以 Middleware2 而言,它的 $next 就是 Middleware1APP 打包成的閉包(參照上圖的最裏面兩層)。閉包

0x03 解答

現給出答案代碼以下:app

// array_reduce 實現
$allMiddleware = [$like, $db];
$go = array_reduce($allMiddleware, function ($next, $middleware) {
    return function () use ($next, $middleware) {
        $middleware($next);
    };
}, $app);
$go();

// foreach 實現
$allMiddleware = [$like, $db];
$next = $app;
foreach ($allMiddleware as $middleware) {
    $next = function () use ($next, $middleware) {
        return $middleware($next);
    };
}
$next();
複製代碼

兩種實現方式但原理同樣,因此只解釋 foreach 版本的實現。首先是將全部的中間件組成一個數組,並將 $next 設置爲 $app,而後開始循環的將 $nextmiddleware 組成一個新的閉包並賦值給 $next,這樣 $next 便不斷的將以前的閉包合併,最後變成一個。而後經過執行最後的 $next 便可得出結果。固然我空口白話的說可能還很差理解,最好的方式是將這代碼本身在大腦執行一遍,還不行就動手畫一下(我第一次看到這個代碼就是這樣才懂的/(ㄒoㄒ)/~~)而後就能夠明白了。 此外不清楚 array_reduce 函數做用的能夠 點此框架

0x04 好點的版本

爲了好理解前面的中間件就只有 $next 參數,但實際框架的中間件都會有相似 $request 參數而且支持返回值 $response(好比 Laravel),下面是一個相似 Laravel 的中間件(僅僅是模仿,照虎畫貓)實現代碼。一法通,萬法通,本菜逼就不解釋了(萬一涉及到個人知識盲區就 GG 了,O(∩_∩)O哈哈~)。函數

$db = function ($request, Closure $next) {
    echo '成功創建數據庫鏈接' . PHP_EOL;
    $response = $next($request);
    echo '成功關閉數據庫鏈接' . PHP_EOL;

    return $response;
};

$like = function ($request, Closure $next) {
    echo '點贊+1' . PHP_EOL;
    $response = $next($request);
    echo '點贊+2' . PHP_EOL;

    return $response;
};

$app = function ($request) {
    echo $request . PHP_EOL;
    return '一個無聊的返回值';
};

$allMiddleware = [$like, $db];
$next = $app;
foreach ($allMiddleware as $middleware) {
    $next = function ($request) use ($middleware, $next) {
        return $middleware($request, $next);
    };
}

$response = $next('O(∩_∩)O');
echo $response;
複製代碼

0x05 總結

(⊙v⊙)嗯,一點點內容就水了這麼多,心滿意足O(∩_∩)O哈哈~。另外文章中若出現錯誤,但願你們可以指出,如有疑問能夠互相討論:-D。post

個人博客原文spa

相關文章
相關標籤/搜索