ThinkPHP6 的自動載入

lxdong12發表於2020-01-13

這是兩篇參考文章

laravel自動載入的描述

PSR0 和 PSR4的區別

事實上,thinkphp 框架用的是 composer 的自動載入

public/index.php 中第一句

namespace think;
require __DIR__ . '/../vendor/autoload.php';

接下來 vender/autoload.php

require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit00730f6ba716ad33a51233e55145c868::getLoader();

這裡引入的 autoload_real.php 中定義的 ComposerAutoloaderInit 類名後面帶了一串雜湊值,可以這樣做的原因可能是為了保證類名的唯一性,防止和使用者自定義的類名衝突

2.1 單例

public static function getLoader()
{
    // 單例模式,這個很好理解
    if (null !== self::$loader) {
        return self::$loader;
    }
    ...
}

2.2 例項化ClassLoader核心類

public static function getLoader()
{
    ...
    spl_autoload_register(array('ComposerAutoloaderInit00730f6ba716ad33a51233e55145c868', 'loadClassLoader'), true, true);
    self::$loader = $loader = new \Composer\Autoload\ClassLoader();
    spl_autoload_unregister(array('ComposerAutoloaderInit00730f6ba716ad33a51233e55145c868', 'loadClassLoader'));
    ...
}

loadClassLoader 方法

public static function loadClassLoader($class)
{
    if ('Composer\Autoload\ClassLoader' === $class) {
        require __DIR__ . '/ClassLoader.php';
    }
}

這裡它先將本類的 loadClassLoader 註冊為自動載入方法,用完後又登出掉,而 loadClassLoader 方法唯一的用處就是引入 ClassLoader.php 檔案,為什麼不直接引入而要繞這麼一圈呢?

前面介紹 laravel自動載入的文章中有對這裡做了解釋,但是我還是看不明白

2.3 將autoload_static.php中定義的自動載入對映關係合併到ClassLoader類中

ClassLoader 類的 prefixLengthsPsr4、prefixDirsPsr4 等屬性是私有的,要操作這些私有屬性,這裡有兩種方式,第一種是使用匿名函式繫結 Closure::bind,第二種是呼叫 ClassLoader 類的公有方法

第二種方式很好理解,我們來看下第一種方式

require_once __DIR__ . '/autoload_static.php';
// ComposerStaticInit00730f6ba716ad33a51233e55145c868::getInitializer 方法返回一個匿名函式物件,call_user_func 執行這個匿名函式
call_user_func(\Composer\Autoload\ComposerStaticInit00730f6ba716ad33a51233e55145c868::getInitializer($loader));

看下 getInitializer 方法

public static function getInitializer(ClassLoader $loader)
{
    return \Closure::bind(function () use ($loader) {
        $loader->prefixLengthsPsr4 = ComposerStaticInit00730f6ba716ad33a51233e55145c868::$prefixLengthsPsr4;
        $loader->prefixDirsPsr4 = ComposerStaticInit00730f6ba716ad33a51233e55145c868::$prefixDirsPsr4;
        $loader->fallbackDirsPsr0 = ComposerStaticInit00730f6ba716ad33a51233e55145c868::$fallbackDirsPsr0;

    }, null, ClassLoader::class);
}

關於 Closure::bind 方法,文件中給的解釋是:複製一個閉包,繫結指定的$this物件和類作用域。不太好理解
第一個引數是要繫結的匿名函式,這個容易理解

第二個引數是匿名函式中 $this 指代的物件,如果需要的話,方式是 new ClassName(),不需要就如文中傳 null

第三個引數指定匿名函式的類作用域,簡單來說就是如果匿名函式中有用到 private 或 protected 的屬性或方法時,這裡傳入對應的類名,如$loader->prefixLengthsPsr4 是私用的,這裡傳的就是 $loader 例項對應的類名。如果將prefixLengthsPsr4
改為公有的,ComposerStaticInit...中的屬性改為私有的,那這個引數就要填ComposerStaticInit...的類名了

我們知道,類物件的傳遞都是引用傳遞,所以這裡匿名函式中修改 $loader 的屬性值,ComposerAutoloaderInit...中定義的 $loader也修改了

2.4 PSR0 和 PSR4 的區別

在類名中使用下劃線沒有任何特殊含義

名稱空間與檔案目錄的對映方法有所調整,假如我們有一個名稱空間: Foo/class ,Foo 是頂級名稱空間,其存在著使用者定義的與目錄的對映關係: "Foo/"=>"src/" 按照PSR0標準,對映後的檔案目錄是:src/Foo/class.php, 但是按照 PSR4標準,對映後的檔案目錄就會是:src/class.php

PSR4的名稱空間最後一位必須是\

PSR0 的最後一個\後如果有_,將會轉化為分隔符

2.5 將ClassLoader類的loadClass方法註冊為自動載入函式

public function register($prepend = false)
{
    // sql_autoload_register 註冊的自動載入函式是一個佇列的形式,如果你有自己的自動載入方法,需要在 composer的自動載入找不到的情況下呼叫,就用 sql_autoload_register 註冊你的,第三個引數傳false
    spl_autoload_register(array($this, 'loadClass'), true, $prepend);
}

2.6 loadClass方法中最主要的findFileWithExtension方法

private function findFileWithExtension($class, $ext)
{
    // PSR-4 lookup
    $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;

    // 先獲取首字母,查詢prefixLengthsPsr4中是否有匹配
    // 擷取最後一個分隔符之前的部分,查詢prefixDirsPsr4中是否有匹配,這裡用的while迴圈,沒有找到的話就繼續擷取上一個分隔符前面部分
    // 問題:為什麼不是從prefixLengthsPsr4首字母對應的值中匹配?難道是因為prefixDirsPsr4的數量不多的原因麼?
    $first = $class[0];
    if (isset($this->prefixLengthsPsr4[$first])) {
        $subPath = $class;
        while (false !== $lastPos = strrpos($subPath, '\\')) {
            $subPath = substr($subPath, 0, $lastPos);
            $search = $subPath . '\\';
            if (isset($this->prefixDirsPsr4[$search])) {
                $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
                foreach ($this->prefixDirsPsr4[$search] as $dir) {
                    if (file_exists($file = $dir . $pathEnd)) {
                        return $file;
                    }
                }
            }
        }
    }

    // PSR-4 fallback dirs
    // 如果上面沒有找到,就去extend資料夾下去找(對這裡的fallbackDirsPsr4 來說就是這樣的),TP的官方文件中也有說在extend中的每一個資料夾都是一個自定義根名稱空間
    foreach ($this->fallbackDirsPsr4 as $dir) {
        if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
            return $file;
        }
    }
    // 後面的是PSR0的匹配,就不看了
    .
    .
    return false;
}

2.7 includeFile

注意下面的註釋部分,這個函式被放在了類的外面,以防止引入的檔案中呼叫$this或self
include和require的區別中有一點,require放在程式的最前面,include可以放在任意位置,所以這裡需要用 include

/**
 * Scope isolated include.
 *
 * Prevents access to $this/self from included files.
 */
function includeFile($file)
{
    include $file;
}
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章