TP5.1 原始碼窺探之類的自動載入機制

zs4336發表於2020-03-24

由於受到疫情影響,小智典營銷,線下課預售暫停一段時間,本來推薦使用微信直播的功能,但由於領導層強烈推薦使用第三方工具小鵝通,導致專案暫緩,僅僅對於線上業務進行開發維護。那就趁有時間學習一下新知識。然而學習什麼新知識不重要,最重要的就是不要不學習。我也很迷茫,不知道先學什麼,總是,這也想學,那也想學,然而到最後還是在原地踏步,痛定思痛,我先定一個計劃,說學什麼就規定在一段時間內就學這個,其他的先不管。學完一個再說。之前面試的時候,經常有人問,有研究過某個框架的原始碼沒。那就先從研究框架的原始碼吧。由於最近的專案是用TP5.1.39開發的,那就研究tp5.1吧。

TP5.1中類的自動載入機制

首先進入專案入口檔案

前提是你安裝了tp5.1.39,composer create-project topthink/think=5.1.* tp5

public\index.php

//1、定義名稱空間
namespace think;

//2、包含並執行base.php檔案
require __DIR__ . '/../thinkphp/base.php';

//3、執行應用並響應
Container::get('app')->run()->send();

進入base.php檔案,看看都做了哪些操作

8qKYlt

1、引入Loader類,需要注意的是,僅僅是引入類,並沒有執行什麼。

8qnsEQ

2、執行Loader::register()方法,註冊自動載入機制
8qOnij

  • 我們進入該方法,首先利用spl_autoload_register註冊系統自動載入機制,含義是:系統在new一個物件的時候,發現類不存在時,就是呼叫Loader::autoload的靜態方法去引入載入對應的類

  • 然後獲取專案的根目錄,設定專案的composer的目錄,引入autoload_static.php檔案,然後獲取已經宣告的類檔案,並彈出最後一個類名,此類名就是autoload_static.php檔案中宣告的類,並將此類的屬性賦值給Loader類的屬性,可以把此步驟理解為註冊app,think\composer名稱空間以及對應的目錄

  • 然後再註冊think,traits名稱空間以及對應的目錄

  • 然後註冊類名對映,runtime/classmap.php,此類檔案是由後期優化生成的,php think optimize:autoload

  • 然後註冊自動載入目錄extend

3、然後執行Error::register()方法

  • 由於think\Error類並沒有事先引入,所以系統呼叫上一步註冊的系統自動載入機制,執行Loader::autoload方法,此時引數為think\Error,進入autoload方法,首先檢視類庫別名中是否有此類名,如果有的話,就使用class_alias函式起別名,並自動載入。如果不存在類庫,那就根據類名去查詢檔案,也就是執行findFile方法,首先檢視是否存在類庫對映(classMap),如果存在就返回檔案目錄地址,不存在的話,就查詢psr-4並返回檔案目錄地址,如果找不到就繼續查詢psr-4自動載入目錄,如果再找不到就查詢psr-0規範的檔案目錄,如果再差找不到,就查詢psr-0自動載入目錄,返回檔案目錄地址,最後引入該檔案

  • 然後執行register方法,註冊異常處理

4、註冊類庫別名,Loader::addClassAlias,方便autoload

Loader::addClassAlias([
    'App'      => facade\App::class, // 對應的類檔案目錄 => thinkphp\library\think\facade\App.php
    'Build'    => facade\Build::class,
    'Cache'    => facade\Cache::class,
    'Config'   => facade\Config::class,
    'Cookie'   => facade\Cookie::class,
    'Db'       => Db::class,
    'Debug'    => facade\Debug::class,
    'Env'      => facade\Env::class,
    'Facade'   => Facade::class,
    'Hook'     => facade\Hook::class,
    'Lang'     => facade\Lang::class,
    'Log'      => facade\Log::class,
    'Request'  => facade\Request::class,
    'Response' => facade\Response::class,
    'Route'    => facade\Route::class,
    'Session'  => facade\Session::class,
    'Url'      => facade\Url::class,
    'Validate' => facade\Validate::class,
    'View'     => facade\View::class,
]);

擴充套件

1、由於autoload_static.php檔案中的類名一直在變化,我們無法得到固定的類名,怎麼做才能將此類的屬性與Loader的屬性合併?

//主要使用兩個函式 get_declared_classes property_exists
//獲取類名
$declaredClass = get_declared_classes();
$composerClass = array_pop($declaredClass);
//屬性合併
foreach (['prefixLengthsPsr4', 'prefixDirsPsr4', 'fallbackDirsPsr4', 'prefixesPsr0', 'fallbackDirsPsr0', 'classMap', 'files'] as $attr) {
    if (property_exists($composerClass, $attr)) {
        self::${$attr} = $composerClass::${$attr};
    }
}

2、為什麼使用名稱空間對映?
節省資源,方便呼叫,避免每次迴圈查詢

3、PSR4 標準頂級名稱空間對映陣列,用了兩個陣列,第一個是用名稱空間第一個字母作為字首索引,然後是 頂級名稱空間,但是最終並不是檔案路徑,而是 頂級名稱空間的長度。為什麼呢?
因為 PSR4 標準是用頂級名稱空間目錄替換頂級名稱空間,所以獲得頂級名稱空間的長度很重要,與Loader::findFile方法中字串擷取拼接類檔案所在位置有用到length

4、如果需要增加一個新的名稱空間,或者新增一個自動載入目錄,你會怎麼做?

主要是使用方法Load::addNamespaceLoader::addAutoLoadDir,在Loader內部外部都可以,但為了不修改原始碼,建議在入口檔案呼叫新增

5、類庫別名class_alias
eg.直接使用Config類的話,自動載入機制會直接呼叫Loader::$classAlias屬性中對應鍵名的鍵值類庫

namespace app\index\controller;

use Config;//此時的Config類,在編輯器中滑鼠點選是跳轉不了的

class Index
{
    public function index()
    {
        $appConfig = Config::get('app.');
        var_dump($appConfig);
    }
}
本作品採用《CC 協議》,轉載必須註明作者和本文連結

趁還沒掉光,趕緊給每根頭髮起個名字吧~

相關文章