繼 生命週期的第二篇,你們儘可放心,不會隨便鴿文章的
第一篇中,咱們提到了入口腳本,也說了,裏面註冊了自動加載的功能php
php 的自動加載是 Loader
類中實現的,這個類在 base.php
中被引入linux
//base .php // 載入Loader類 require __DIR__ . '/library/think/Loader.php'; // 註冊自動加載 Loader::register();
咱們程序在這裏執行了 Loader 中靜態方法 ,同時這也是一個所有的類register()
咱們進入 Loader.php
,按照上面執行順序看看其核心是什麼?web
此方法行數過長,咱們一點一點來分析thinkphp
// 註冊系統自動加載 spl_autoload_register($autoload ?: 'think\\Loader::autoload', true, true);
這就是註冊咱們的自動加載函數,$autoload
這個變量是傳的參數,考慮到你能夠本身實現本身的加載類,爲了方便拓展,TP可讓你本身實現本身的類加載方法。數組
若是不瞭解這個函數的同窗,請看文章最頂部的那個鏈接,上面有詳細講解。緩存
$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 也順帶一塊兒註冊了,方便對拓展的調用。app
autoload_static.php中的變量加載進內存中有一個難題:因爲autoload_static.php 文件中的類名一直在變化,咱們沒法獲得固定的類名。(如我係統中 類名爲 ComposerStaticInit5109814b18095308ffe89ba7a1be18df
)composer
爲了把 require self::$composerPath . 'autoload_static.php';
中 的屬性 載入進程序中,在這裏咱們換了一種形式框架
首先,獲取程序中加載的全部類名,而後取咱們最後一個加載的類名(即數組中的最後一個)。函數
$declaredClass = get_declared_classes(); $composerClass = array_pop($declaredClass);
拿到了咱們的類名,調用 property_exists($composerClass, $attr)
檢查類中是否存在指定的屬性
foreach (['prefixLengthsPsr4', 'prefixDirsPsr4', 'fallbackDirsPsr4', 'prefixesPsr0', 'fallbackDirsPsr0', 'classMap', 'files'] as $attr) 中後面 ('fallbackDirsPsr4', 'prefixesPsr0', 'fallbackDirsPsr0', 'classMap', 'files')的做用是什麼?
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__ . '/../..' , …… )
直接命名空間全名與目錄的映射,簡單粗暴,也致使這個數組至關的大。
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的目錄規則重寫了文件路徑
以後再加咱們的目錄文件