繼 生命週期的第二篇,大家儘可放心,不會隨便鴿文章的
第一篇中,我們提到了入口腳本,也說了,裏面註冊了自動加載的功能
- 本文默認你有自動加載和命名空間的基礎。如果沒有請 看此篇文章 php 類的自動加載與命名空間
自動加載機制
php 的自動加載是 Loader
類中實現的,這個類在 base.php
中被引入
//base .php
// 載入Loader類
require __DIR__ . '/library/think/Loader.php';
// 註冊自動加載
Loader::register();
我們程序在這裏執行了 Loader 中靜態方法 ,同時這也是一個全部的類register()
我們進入 Loader.php
,按照上面執行順序看看其核心是什麼?
register()方法執行流程
註冊系統自動加載
此方法行數過長,我們一點一點來分析
// 註冊系統自動加載
spl_autoload_register($autoload ?: 'think\\Loader::autoload', true, true);
這就是註冊我們的自動加載函數,$autoload
這個變量是傳的參數,考慮到你可以自己實現自己的加載類,爲了方便拓展,TP可以讓你自己實現自己的類加載方法。
如果不瞭解這個函數的同學,請看文章最頂部的那個連接,上面有詳細講解。
Composer自動加載支持
$rootPath = self::getRootPath();
self::$composerPath = $rootPath . 'vendor' . DIRECTORY_SEPARATOR . 'composer' . DIRECTORY_SEPARATOR;
// Composer自動加載支持
if (is_dir(self::$composerPath)) {
if (is_file(self::$composerPath . 'autoload_static.php')) {
require self::$composerPath . 'autoload_static.php';
// 獲取當前加載的所有類
$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};
}
}
} else {
self::registerComposerLoader(self::$composerPath);
}
}
爲了支持 composer 拓展,在自動註冊時候,把composer 也順帶一起註冊了,方便對拓展的調用。
autoload_static.php中的變量加載進內存中有一個難題:由於autoload_static.php 文件中的類名一直在變化,我們無法得到固定的類名。(如我係統中 類名爲 ComposerStaticInit5109814b18095308ffe89ba7a1be18df
)
爲了把 require self::$composerPath . 'autoload_static.php';
中 的屬性 載入進程序中,在這裏我們換了一種形式
首先,獲取程序中加載的所有類名,然後取我們最後一個加載的類名(即數組中的最後一個)。
$declaredClass = get_declared_classes();
$composerClass = array_pop($declaredClass);
拿到了我們的類名,調用 property_exists($composerClass, $attr)
檢查類中是否存在指定的屬性
疑問: composer_static 的參數代表是什麼?
foreach (['prefixLengthsPsr4', 'prefixDirsPsr4', 'fallbackDirsPsr4', 'prefixesPsr0', 'fallbackDirsPsr0', 'classMap', 'files'] as $attr) 中後面 ('fallbackDirsPsr4', 'prefixesPsr0', 'fallbackDirsPsr0', 'classMap', 'files')的作用是什麼?
classMap(命名空間映射)
public static $classMap = array (
'App\\Http\\Controllers\\Auth\\ForgotPasswordController'
=> __DIR__ . '/../..' . '/app/Http/Controllers/Auth/ForgotPasswordController.php',
'App\\Http\\Controllers\\Auth\\LoginController'
=> __DIR__ . '/../..' . '/app/Http/Controllers/Auth/LoginController.php',
'App\\Http\\Controllers\\Auth\\RegisterController'
=> __DIR__ . '/../..' ,
……
)
直接命名空間全名與目錄的映射,簡單粗暴,也導致這個數組相當的大。
PSR4 標準頂級命名空間映射數組:
public static $prefixLengthsPsr4 = array(
'p' => array (
'phpDocumentor\\Reflection\\' => 25,
),
'S' => array (
'Symfony\\Polyfill\\Mbstring\\' => 26,
'Symfony\\Component\\Yaml\\' => 23,
'Symfony\\Component\\VarDumper\\' => 28,
...
),
...);
public static $prefixDirsPsr4 = array (
'phpDocumentor\\Reflection\\' => array (
0 => __DIR__ . '/..' . '/phpdocumentor/reflection-common/src',
1 => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src',
2 => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src',
),
'Symfony\\Polyfill\\Mbstring\\' => array (
0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring',
),
'Symfony\\Component\\Yaml\\' => array (
0 => __DIR__ . '/..' . '/symfony/yaml',
),
...)
PSR4 標準頂級命名空間映射用了兩個數組,第一個是用命名空間第一個字母作爲前綴索引,然後是 頂級命名空間,但是最終並不是文件路徑,而是 頂級命名空間的長度。爲什麼呢?
因爲 PSR4 標準是用頂級命名空間目錄替換頂級命名空間,所以獲得頂級命名空間的長度很重要。
具體說明這些數組的作用:
假如我們找 Symfony\Polyfill\Mbstring\example
這個命名空間,通過前綴索引和字符串匹配我們得到了
'Symfony\\Polyfill\\Mbstring\\' => 26,
這條記錄,鍵是頂級命名空間,值是命名空間的長度。拿到頂級命名空間後去 $prefixDirsPsr4
數組 獲取它的映射目錄數組:(注意映射目錄可能不止一條)
<?php
'Symfony\\Polyfill\\Mbstring\\' => array (
0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring',
)
然後我們就可以將命名空間 Symfony\\Polyfill\\Mbstring\\example
前26個字符替換成目錄 __DIR__ . '/..' . '/symfony/polyfill-mbstring
,我們就得到了__DIR__ . '/..' . '/symfony/polyfill-mbstring/example.php
,先驗證磁盤上這個文件是否存在,如果不存在接着遍歷。如果遍歷後沒有找到,則加載失敗。
注: 其實作爲一個web框架,composer裏面的東西,不應該由ThinkPHP關心的,但由於 TP5 自己原生的框架包 的設計沒有完全包容 composer, 所在註冊自動加載的時候會拿去其屬性值自己來使用(僅限自己理解,如果與您觀點不同歡迎討論)
註冊命名空間定義
// 註冊命名空間定義
self::addNamespace([
'think' => __DIR__,
'traits' => dirname(__DIR__) . DIRECTORY_SEPARATOR . 'traits',
]);
// 加載類庫映射文件
if (is_file($rootPath . 'runtime' . DIRECTORY_SEPARATOR . 'classmap.php')) {
self::addClassMap(__include_file($rootPath . 'runtime' . DIRECTORY_SEPARATOR . 'classmap.php'));
}
// 自動加載extend目錄
self::addAutoLoadDir($rootPath . 'extend');
這後面的代碼都大同小異,都是把 所需要用到的類,映射到Psr4空間
這個靜態變量中。到時候方便我們使用命名空間進行調用。
// 加載類庫映射文件
if (is_file($rootPath . 'runtime' . DIRECTORY_SEPARATOR . 'classmap.php')) {
self::addClassMap(__include_file($rootPath . 'runtime' . DIRECTORY_SEPARATOR . 'classmap.php'));
}
在 TP5 代碼下執行php think optimize:autoload
就會在runtime下生成 classmap.php 文件,文件形式
return [
'app\\index\\controller\\Index' => 'D:/app/tp5/application/' . 'index/controller/Index.php',
'think\\App' => 'D:/app/tp5/thinkphp/library/' . '/think/App.php',
'think\\Build' => 'D:/app/tp5/thinkphp/library/' . '/think/Build.php',
'think\\Cache' => 'D:/app/tp5/thinkphp/library/' . '/think/Cache.php',
'think\\Collection' => 'D:/app/tp5/thinkphp/library/' . '/think/Collection.php',
...
]
生成類庫映射文件,會在runtime
目錄下面生成classmap.php
文件,生成的類庫映射文件會掃描系統目錄和應用目錄的類庫。在之後碰到了之後直接拿來用,提高系統自動加載的性能。
register()
函數這裏就大概分析結束了。 這裏我們就講完了 註冊自動加載。
使用自動加載
我們在 register
中定義了我們自動加載函數式 Loader::autoload()
方法。 我們就小試牛刀,在我們的 base.php
中,我們加載完 自動加載機制後,就會加載我們的異常處理
// 載入Loader類
require __DIR__ . '/library/think/Loader.php';
// 註冊自動加載
Loader::register();
// 註冊錯誤和異常處理機制
Error::register();
在這時的狀態裏 Error 不存在,所有會進入我們的自動加載方法中重新試一下。
//函數整體內容
public static function autoload($class)
{
if (isset(self::$classAlias[$class])) {
return class_alias(self::$classAlias[$class], $class);
}
if ($file = self::findFile($class)) {
// Win環境嚴格區分大小寫
if (strpos(PHP_OS, 'WIN') !== false && pathinfo($file, PATHINFO_FILENAME) != pathinfo(realpath($file), PATHINFO_FILENAME)) {
return false;
}
__include_file($file);
return true;
}
}
我們截取片段一點一點分析。
if (isset(self::$classAlias[$class])) {
return class_alias(self::$classAlias[$class], $class);
}
這一段是判斷我們我們是否對該類設置別名,但明顯我們此時還沒有設置。
if ($file = self::findFile($class)) {
// Win環境嚴格區分大小寫
if (strpos(PHP_OS, 'WIN') !== false && pathinfo($file, PATHINFO_FILENAME) != pathinfo(realpath($file), PATHINFO_FILENAME)) {
return false;
}
__include_file($file);
return true;
}
findFile($class)
如果我們之前緩存了 classMap 在runtime文件夾下,那麼他會直接返回。(這也就是爲什麼我們緩存 classMap 會提升性能的原因),如果沒有緩存就配合我們之前存儲映射關係的靜態數組prefixDirsPsr4
,和 prefixLengthsPsr4
來找尋文件的目錄,速度會相對慢很多。 如果沒有找到那麼就返回空, spl_autoload_register
會判斷沒有找到該類,拋出錯誤。
如果找到就消除 linux 和 window 對路徑名稱的差異。(linux 嚴格區分大小寫,而win 沒有嚴格區分)
這裏主要是擔心在window環境下,路徑名稱大小寫沒分,所以我們根據linux的目錄規則重寫了文件路徑
之後再加我們的目錄文件