ThinkPHP6 原始碼分析之中介軟體分析

JaguarJack發表於2019-07-04

中介軟體

從請求過程中可以看出,第一步就是載入的中介軟體。那麼如何載入的呢?看下面這段程式碼

$this->app->middleware

讓 app 例項訪問屬性 middleware?你會發現例項中並沒有這個屬性,那麼訪問一個不存在的屬性會發生什麼呢?它會去訪問 __get 魔術方法,你有這個想法之後會在 Container 中發現這個魔術方法,最終它會去 make 建立物件,對於 make 的過程請到 解析 Request 章節檢視。
建立 middleware 的過程中有一個細節,就是載入配置檔案 middleware.php,進入到 think\Middleware 檔案之後,你會發現這麼一段程式碼。

public static function __make(App $app, Config $config)
{
    return (new static($config->get('middleware')))->setApp($app);
}

正如之前所提到的那樣,使用 make 方法建立物件會執行預設方法 __make,所以這個時候就會將 config 中的 middleware.php 載入進來。目前這個版本似乎沒有加入這個配置檔案,你可以手動新增。

來看下面一段程式碼,從這段程式碼引出中介軟體的載入過程。

$this->app->middleware->import(include $this->app->getBasePath() . 'middleware.php');

在整個請求過程,首先載入的是 app/middleware.php 檔案中的中介軟體,預設提供了四個中介軟體。但是這些都是不載入,你可自行選擇開啟。然後來看看 import 如何載入的。

public function import(array $middlewares = [], string $type = 'route'): void
{
    foreach ($middlewares as $middleware) {
        $this->add($middleware, $type);
    }
}

$type 引數標記了路由的型別,目前只看到 routecontroller 兩種型別。預設全域性中介軟體是 route 型別的。add 方法才是實實在在的匯入。來看看的這個方法做了哪些事兒。

public function add($middleware, string $type = 'route'): void
{
    // null 直接返回
    if (is_null($middleware)) {
        return;
    }
    // 建立中介軟體
    $middleware = $this->buildMiddleware($middleware, $type);
    // 加入到中介軟體佇列,佇列也是分型別的
    if ($middleware) {
        $this->queue[$type][] = $middleware;
    }
}

buildMiddleware 方法用來建立中介軟體,做一些解析的工作。來看看程式碼的過程,直接在註釋中解釋

protected function buildMiddleware($middleware, string $type = 'route'): array
{
    // 陣列型別的 第一個引數必須是中介軟體,第二個是傳入的引數
    if (is_array($middleware)) {
        list($middleware, $param) = $middleware;
    }
    // 如果是 middleware 是閉包, 直接返回
    if ($middleware instanceof \Closure) {
        return [$middleware, $param ?? null];
    }
    // 不支援非字串的型別
    if (!is_string($middleware)) {
        throw new InvalidArgumentException('The middleware is invalid');
    }
    // 支援鍵值對,正如官方手冊中提到,例如這樣 [ 'hello' => middleware ]
    if (isset($this->config[$middleware])) {
        $middleware = $this->config[$middleware];
    }
    // 當你從配置檔案解析出來是陣列格式,就遞迴呼叫
    if (is_array($middleware)) {
        $this->import($middleware, $type);
        return [];
    }
    // 最後返回一個 middware 的類名和預設方法 'handle'
   // handle 方法對中介軟體而言是必須的,不然無法執行
    return [[$middleware, 'handle'], $param ?? null];
}

然後返回會加入到 Queue 佇列中,這個時候你開啟全域性中介軟體的其中一個話,佇列中將會是這樣的內容。

array(1) {
  ["route"]=>
  array(1) {
    [0]=>
    array(2) {
      [0]=>
      array(2) {
        [0]=>
        string(34) "think\middleware\CheckRequestCache"
        [1]=>
        string(6) "handle"
      }
      [1]=>
      NULL
    }
  }
}

這個和我們預期是一樣的,在程式碼解釋過程也沒有遇到任何阻礙。我們繼續往下看,這裡僅僅是加入的過程,在後面請求執行的時候來看看如何執行的這些中介軟體的。

中介軟體執行

中介軟體的是由 Diapatch 方法執行,而核心在 resolve 方法,來看一下這個方法做了什麼。

protected function resolve(string $type = 'route')
{
    return function (Request $request) use ($type) {
        $middleware = array_shift($this->queue[$type]);

        if (null === $middleware) {
            throw new InvalidArgumentException('The queue was exhausted, with no response returned');
        }
        // 1
        list($call, $param) = $middleware;
        // 2
        if (is_array($call) && is_string($call[0])) {
            $call = [$this->app->make($call[0]), $call[1]];
        }

        try {
             // 3
            $response = $this->app->invoke($call, [$request, $this->resolve($type), $param]);
        } catch (HttpResponseException $exception) {
            $response = $exception->getResponse();
        }

        if (!$response instanceof Response) {
            throw new LogicException('The middleware must return Response instance');
        }

        return $response;
    };
}

按照 1,2,3 的步驟進行說明。
佇列使用了 array_shift 來進行,說明會執行先進來的中介軟體。所以你可以在到達控制器之前做很多業務應用初始化的事情。

  • 從上面可以知道中介軟體佇列的資料結構,所以步驟以就是解析出來 middleware 中介軟體類和引數。
  • 如果是陣列結構直接解析出物件
  • 最重要的就是第三步了,利用反射來執行中介軟體類,注意在這個執行過程中遞迴呼叫了,而且記住 resolve 返回的始終是閉包,然後在中介軟體之間傳遞,你可以觀察中介軟體的 handle 方法的引數,Request 物件,第二個閉包,第三個可選引數,正好對上了這個陣列引數介面,所以 handle 方法的 $next() 就是在消費佇列。

所以簡單來說,中介軟體的過程就是利用佇列在執行消費。保證了中介軟體的順序執行。

文章轉載於 thinkphp6原始碼分析之中介軟體分析

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章