ThinkPHP5.1 源碼淺析(二)自動加載機制

繼 生命週期的第二篇,大家儘可放心,不會隨便鴿文章的

第一篇中,我們提到了入口腳本,也說了,裏面註冊了自動加載的功能

自動加載機制

php 的自動加載是 Loader 類中實現的,這個類在 base.php 中被引入

//base .php
// 載入Loader類
require __DIR__ . '/library/think/Loader.php';

// 註冊自動加載
Loader::register();

我們程序在這裏執行了 Loader 中靜態方法 ,同時這也是一個全部的類register() 我們進入 Loader.php ,按照上面執行順序看看其核心是什麼?

register()方法執行流程

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的目錄規則重寫了文件路徑

之後再加我們的目錄文件

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章