Thinkphp5類載入機制
我一直對於thinkphp5的載入執行的時候做了什麼,他是怎麼自動載入類的,是和composer一樣的嗎—能否實現執行時再載入類,於是做了一下測試,追蹤了整個載入流程。
以檔案為單位進行講解:
入口檔案index.php
<?php
// 定義應用目錄
define('APP_PATH', __DIR__ . '/../application/');
// 載入框架引導檔案
require __DIR__ . '/../thinkphp/start.php';
1. 定義了APP_PATH常量
2. 載入start.php檔案
start.php檔案
<?php namespace think; // ThinkPHP 引導檔案 // 載入基礎檔案 require __DIR__ . '/base.php'; // 執行應用 App::run()->send();
1. 載入base.php
2. 執行App類的靜態方法run(),再執行返回值的send()方法
來到這裡,出現兩個問題,App類。。。是從哪裡載入進來的呢?看看base.php
base.php檔案
<?php define('THINK_VERSION', '5.0.3'); define('THINK_START_TIME', microtime(true)); define('THINK_START_MEM', memory_get_usage()); define('EXT', '.php'); define('DS', DIRECTORY_SEPARATOR); defined('THINK_PATH') or define('THINK_PATH', __DIR__ . DS); define('LIB_PATH', THINK_PATH . 'library' . DS); define('CORE_PATH', LIB_PATH . 'think' . DS); define('TRAIT_PATH', LIB_PATH . 'traits' . DS); defined('APP_PATH') or define('APP_PATH', dirname($_SERVER['SCRIPT_FILENAME']) . DS); defined('ROOT_PATH') or define('ROOT_PATH', dirname(realpath(APP_PATH)) . DS); defined('EXTEND_PATH') or define('EXTEND_PATH', ROOT_PATH . 'extend' . DS); defined('VENDOR_PATH') or define('VENDOR_PATH', ROOT_PATH . 'vendor' . DS); defined('RUNTIME_PATH') or define('RUNTIME_PATH', ROOT_PATH . 'runtime' . DS); defined('LOG_PATH') or define('LOG_PATH', RUNTIME_PATH . 'log' . DS); defined('CACHE_PATH') or define('CACHE_PATH', RUNTIME_PATH . 'cache' . DS); defined('TEMP_PATH') or define('TEMP_PATH', RUNTIME_PATH . 'temp' . DS); defined('CONF_PATH') or define('CONF_PATH', APP_PATH); // 配置檔案目錄 defined('CONF_EXT') or define('CONF_EXT', EXT); // 配置檔案字尾 defined('ENV_PREFIX') or define('ENV_PREFIX', 'PHP_'); // 環境變數的配置字首 // 環境常量 define('IS_CLI', PHP_SAPI == 'cli' ? true : false); define('IS_WIN', strpos(PHP_OS, 'WIN') !== false); // 載入Loader類 require CORE_PATH . 'Loader.php'; // 載入環境變數配置檔案 if (is_file(ROOT_PATH . '.env')) { $env = parse_ini_file(ROOT_PATH . '.env', true); foreach ($env as $key => $val) { $name = ENV_PREFIX . strtoupper($key); if (is_array($val)) { foreach ($val as $k => $v) { $item = $name . '_' . strtoupper($k); putenv("$item=$v"); } } else { putenv("$name=$val"); } } } // 註冊自動載入 \think\Loader::register(); // 註冊錯誤和異常處理機制 \think\Error::register(); // 載入慣例配置檔案 \think\Config::set(include THINK_PATH . 'convention' . EXT);
1. 定義了許多常量
2. 載入Loader類
3. 檢視有無“環境變數配置檔案”,有的話就載入
4. 執行Loader類的register()方法
5. 執行Error類的register()方法
從上下文以及註釋可以知道,應該是Loader類的靜態方法register()方法,實現了類的自動載入。重點就是它了。
Loader類的靜態方法register()方法
// 註冊自動載入機制 public static function register($autoload = '') { // 註冊系統自動載入 spl_autoload_register($autoload ?: 'think\\Loader::autoload', true, true); // 註冊名稱空間定義 self::addNamespace([ 'think' => LIB_PATH . 'think' . DS, 'behavior' => LIB_PATH . 'behavior' . DS, 'traits' => LIB_PATH . 'traits' . DS, ]); // 載入類庫對映檔案 if (is_file(RUNTIME_PATH . 'classmap' . EXT)) { self::addClassMap(__include_file(RUNTIME_PATH . 'classmap' . EXT)); } // Composer自動載入支援 if (is_dir(VENDOR_PATH . 'composer')) { self::registerComposerLoader(); } // 自動載入extend目錄 self::$fallbackDirsPsr4[] = rtrim(EXTEND_PATH, DS); }
1. 預設$autoload為空,使用PHP內建函式spl_autoload_register註冊Loader類的靜態方法autoload()方法為自動載入機制。
其意思即是,當使用類的時候,找不到類或者還沒有包含,會將類名傳給autoload()方法,讓它處理
2. 註冊名稱空間定義,是將名稱空間與真實的檔案路徑對應一一對應,並且儲存在一個類靜態變數數組裡面。
其作用是,在autoload()方法接收到類的名字的時候,通過類所屬的名稱空間,找到類檔案的真實路徑,進而將類檔案包含include
3. 以下的基本是一樣的載入,只不過不是載入框架自身的類檔案,而是載入composer庫或者自己寫的類
autoload()方法
// 自動載入
public static function autoload($class)
{
// 檢測命名空間別名
if (!empty(self::$namespaceAlias)) {
$namespace = dirname($class);
if (isset(self::$namespaceAlias[$namespace])) {
$original = self::$namespaceAlias[$namespace] . '\\' . basename($class);
if (class_exists($original)) {
return class_alias($original, $class, false);
}
}
}
if ($file = self::findFile($class)) {
// Win環境嚴格區分大小寫
if (IS_WIN && pathinfo($file, PATHINFO_FILENAME) != pathinfo(realpath($file), PATHINFO_FILENAME)) {
return false;
}
__include_file($file);
return true;
}
}
如你所見,autoload方法就是接收類名,搜尋類檔案,包含類檔案。
恩,至此Thinkphp5執行時載入類的流程已經明瞭。
總結一下,index.php載入start.php,在start.php裡面載入base.php(類自動載入機制就在這裡出現了),然後下面呼叫App的靜態方法run方法執行“模組/控制器/操作”,返回Respose類的例項執行send方法,將響應資料傳送給客戶端,這樣,一個完整的請求就完成了。