ThinkPHP6 原始碼閱讀(二):Request 類是如何例項化的

tsin發表於2019-08-11

run()方法

接上一篇,得到Http類的一個例項後,程式接下來執行$response = $http->run();run()方法程式碼如下:

public function run(Request $request = null): Response
{
    //自動建立request物件
    $request = $request ?? $this->app->make('request', [], true);
    // 將Request類的例項儲存到「$instances」陣列
    $this->app->instance('request', $request);

    try {
        $response = $this->runWithRequest($request);
    } catch (Throwable $e) {
        $this->reportException($e);

        $response = $this->renderException($request, $e);
    }

    return $response->setCookie($this->app->cookie);
}

從「request」標識找到要例項化的類

run()方法的第一行通過容器類例項app呼叫make()方法並傳入Request類的標識來例項化Request類。具體過程如下分析。

通過make()方法首先解析得到request標識對應的標識think\App, 進一步遞迴解析,又得到app\Request類——這個才是最終要例項化的類。
app\Request類對應的檔案位於app目錄下,程式碼如下:

namespace app;

class Request extends \think\Request
{

}

實際上它啥事也沒幹,直接繼承系統的\think\Request。當然我們也可以在這裡對系統的Request類進行改寫重構。

呼叫invokeClass()方法

從類的標識解析得到最終需要例項化的類(單例模式下,且該類還不存在例項)之後,程式呼叫invokeClass()方法,通過PHP的反射類實現類的例項化。由於\think\Request類存在__make()方法,所以例項化之前首先呼叫該方法。__make()方法程式碼如下:

public static function __make(App $app)
{
    //例項化自身
    $request = new static();

    // 儲存超全域性變數$_SERVER
    // 參考https://www.php.net/manual/zh/reserved.variables.server.php
    $request->server  = $_SERVER;

    // 跟前面的Http的例項化原理一樣,例項化Env類並儲存
    $request->env     = $app->env;

    $request->get     = $_GET;
    $request->post    = $_POST ?: $request->getInputData($request->input);
    $request->put     = $request->getInputData($request->input);
    $request->request = $_REQUEST;
    $request->cookie  = $_COOKIE;
    $request->file    = $_FILES ?? [];

    // 如果存在方法apache_request_headers則執行之
    // apache_request_headers的作用是獲取所有HTTP請求頭
    if (function_exists('apache_request_headers') && $result = apache_request_headers()) {
        $header = $result;
    } else {
        $header = [];
        $server = $_SERVER;
        foreach ($server as $key => $val) {
            if (0 === strpos($key, 'HTTP_')) {
                $key          = str_replace('_', '-', strtolower(substr($key, 5)));
                $header[$key] = $val;
            }
        }
        if (isset($server['CONTENT_TYPE'])) {
            $header['content-type'] = $server['CONTENT_TYPE'];
        }
        if (isset($server['CONTENT_LENGTH'])) {
            $header['content-length'] = $server['CONTENT_LENGTH'];
        }
    }

    //將陣列的中所有KEY轉為小寫
    $request->header = array_change_key_case($header);
    //__make()方法最終返回Request類的例項
    return $request;
}

__make()方法首先例項化think\Request類自身。think\Request類建構函式如下:

public function __construct()
{
    // 儲存 php://input
    //參考資料:http://www.nowamagic.net/academy/detail/12220520
    // php://input 用於讀取POST資料
    //(可用於Coentent-Type取值為application/x-www-data-urlencoded、text/json、text/xml,
    // 不能用於multipart/form-data型別)
    //用$_POST的話,僅在Coentent-Type取值為application/x-www-data-urlencoded
    // 和multipart/form-data兩種情況下有用
    $this->input = file_get_contents('php://input');
}

建構函式讀取了php://input儲存起來。接著,__make()方法儲存了一些請求相關的資料,最後返回一個Request類例項。最後的最後, make()方法也成功得到該例項,整個過程跟Http類的例項化類似。該Request類例項部分成員變數如圖:

ThinkPHP6 原始碼閱讀(二):Request類是如何例項化的

儲存「Request」類的例項到「$instance」陣列

得到Request類的例項後,run()方法接著將該例項儲存到「$instance」陣列,以便後面單例模式要用到時可以直接獲取。$instances陣列的值如圖,Request類的例項已儲存在裡面:

ThinkPHP6 原始碼閱讀(二):Request類是如何例項化的

Was mich nicht umbringt, macht mich stärker

相關文章