Laravel 原始碼筆記 Composer 自動載入

php_yt發表於2020-03-02

前言

寫這一篇文章,整整花了兩天時間,一直在調整書寫的順序,怎麼樣易懂,不對的地方請大佬指正。

程式地址

index.php

require __DIR__.'/../vendor/autoload.php';
跟蹤:index.php > /vendor/autoload.php

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

到達 composer 工作區
O3dzaRYtmb.png!large

什麼是自動載入?

新下載的 laravel5.5 ,App 下預設有 User.php,是 User 模型(Model)

$userModel = new App\User();use App\User;
$userModel = new User();

如此方便得益於 composer 的自動載入。

composer 自動載入的流程

(一)類檔案歸納 驅動:composer

根據載入標準(files\classmap\psr-4\psr-0),將對映關係分門別類寫入不同的檔案,以陣列儲存

當在控制檯執行 composer require | update 引入一個元件時包,讀取組建包的 composer.json 中的 "autoload"配置,分別按照每個組建包配置的自動載入規則,掃描元件中的檔案,將其寫入不同載入標準的php檔案陣列中

那麼composer.json 中的自動載入通常有下面幾種方式 :(這不是真實的 autoload 配置,這是從 laravel5.5 各個組建包的 composer.json 中拼在一起的)

"autoload": {
        這是 laravel/framework 組建包的 composer.json 中的
        "files": [
            "src/Illuminate/Foundation/helpers.php",
            "src/Illuminate/Support/helpers.php"
        ],
        這是 hamcrest/hamcrest-php 組建包的 composer.json 中的
        "classmap": [
            "hamcrest"
        ]
        這是專案的 composer.json 中的
        "psr-4": {
            "App\\": "app/"
        },
        這是 mockery/mockery 組建包的 composer.json 中的
        "psr-0": {
            "Mockery": "library/"
        }
    },

files : 檔案預載入,框架啟動時便被 include ,通常檔案中提供一些函式方法方便我們使用,如經常用的 dd()
"files:["src/Illuminate/Foundation/helpers.php"]" 寫入 /composer/autoload_files.php

autoload_files.php >
<?php
return array(
    ...
    'f0906e6318348a765ffb6eb24e0d0938' => $vendorDir . '/laravel/framework/src/Illuminate/Foundation/helpers.php',
);

classmap : 直接對映檔案真實路徑。 這是簡單粗暴的,因此這種方式效率是最高的。
"classmap": ["database/seeds",], 寫入 /composer/autoload_classmap.php

autoload_classmap.php >
return array(
    'Hamcrest\\Arrays\\IsArray' => $vendorDir . '/hamcrest/hamcrest-php/hamcrest/Hamcrest/Arrays/IsArray.php',
    'Hamcrest\\Arrays\\IsArrayContaining' => $vendorDir . '/hamcrest/hamcrest-php/hamcrest/Hamcrest/Arrays/IsArrayContaining.php',
    'Hamcrest\\Arrays\\IsArrayContainingInAnyOrder' => $vendorDir . '/hamcrest/hamcrest-php/hamcrest/Hamcrest/Arrays/IsArrayContainingInAnyOrder.php',
    ...// hamcrest 資料夾下所有檔案
);

use Hamcrest\\Arrays\\IsArray; 時,可直接在 classmap 的陣列中找到它,是不是很粗暴呢。

psr-4 :這是最常用的載入標準。"psr-4": {"App\\": "app/"} 寫入 autoload_psr4.php

/composer/autoload_psr4.php >
return array(
    ...
    'App\\' => array($baseDir . '/app'),// App 稱作 prefix
);

只要是 app/ 目錄下的檔案,且名命空間符合 psr-4 標準的類檔案都能被自動載入。如use App\Test => app/Test.php 。而 classmap 方式不可以,這樣你可以在 app/ 下自由的增加/刪除類檔案了。

這裡提一下,前面說 classmap 方式是最高效的,而 composer dump-autoload 可以將通過 psr-4 規範載入的檔案 “落盤”,即寫入 autoload_classmap.php 。從而起到加速的作用,官方建議生產環境時執行 composer dump-autoload --optimize 來優化專案的自動載入速度。

為了說明 composer dump-autoload 的作用,我在 app 目錄下建立 Test.php

<?php
namespace App;
class Test{  
}

執行 composer dump-autoload 後,發現在autoload_classmap.phpautoload_static.php(稍後再提及)中找到了它。

/composer/autoload_classmap.php && autoload_static.php >
return array(
    ...
    'App\\Test' => __DIR__ . '/../..' . '/app/Test.php',
    'App\\User' => $baseDir . '/app/User.php',
)

psr-0 :和 psr-4 類似,只是載入規則有所不同。官方已棄用,但 laravel 有的組建包還是在用的,composer 仍然支援向下相容。"psr-0": {"Mockery": "library/"} 寫入 autoload_namespaces.php

/composer/autoload_namespaces.php >
return array(
    ...
    'Mockery' => array($vendorDir . '/mockery/mockery/library'),
);

autoload_static.php
回顧 composer/ 目錄:
O3dzaRYtmb.png!large
其中四個檔案中儲存了四種規範的對映,還有一個 autoload_static.php 檔案。它把這四個檔案中的所有對映集中在一起,通過 getInitializer() 方法注入到 ClassLoader的屬性中( ClassLoader 是實現自動載入的類,稍後再提)

autoload_static.php >
<?php
namespace Composer\Autoload;
class ComposerStaticInitccb56ced66f82d50b9e1d3fd28a6ab26{
    public static $files = array(..);
    public static $classMap = array(..);
    public static $prefixLengthsPsr4 = array(..);
    public static $prefixDirsPsr4 = array(..);
    public static $fallbackDirsPsr4 = array(..);
    public static $prefixesPsr0 = array(..);
    public static function getInitializer(ClassLoader $loader){..};
}

(二) 類檔案提取 驅動:/composer/autoload_real.php

從四個檔案中 或 autoload_static.php 中(因它包含了四個檔案的全部對映)將全部對映提取到實現自動載入的類(ClassLoader)中,由 ClassLoader 查詢類的對映,實現自動載入,

index.php > /vendor/autoload.php > /composer/autoload_real.php >

//重要概念:當前類的任務是把所有對映注入到ClassLoader中,由 ClassLoader 類實現自動載入。為了更好的說明,以下為簡化版程式碼。
class ComposerAutoloaderInitccb56ced66f82d50b9e1d3fd28a6ab26{
    getLoader(){
        ----STEP1 例項化 ClassLoader-------
        require __DIR__ . '/ClassLoader.php';
        $loader = new ClassLoader();

        ----STEP2 提取對映並注入 ClassLoader-------
        if ($useStaticLoader) {
            //若符合執行環境,優先從 autoload_static.php 提取對映(見前面 autoload_static.php 的介紹)
            require_once __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInitccb56ced66f82d50b9e1d3fd28a6ab26::getInitializer($loader));

        } else {
            // 否則就從分別從三個檔案中提取出來並注入 ClassLoader
            //psr-0 
            $map = require __DIR__ . '/autoload_namespaces.php';
            foreach ($map as $namespace => $path) {
                $loader->set($namespace, $path);
            }

            //psr-4
            $map = require __DIR__ . '/autoload_psr4.php';
            foreach ($map as $namespace => $path) {
                $loader->setPsr4($namespace, $path);
            }

            //classmap
            $classMap = require __DIR__ . '/autoload_classmap.php';
            if ($classMap) {
                $loader->addClassMap($classMap);
            }
        }

        ----STEP3  STEP2完畢,啟動 ClassLoader 的自動載入方法-------  
        $loader->register(true); //spl_autoload_register

        ----STEP4  處理預載入檔案-------  
        //處理 autoload_files.php 中的預載入檔案,由於這些檔案需要立即載入,它和類的自動載入是不同的,只要載入後,檔案中的函式就能用。
        //同理,能從 autoload_static.php 提取,優先從這個檔案提取。
        if ($useStaticLoader) {
            $includeFiles = Composer\Autoload\ComposerStaticInitccb56ced66f82d50b9e1d3fd28a6ab26::$files;
        } else {
            $includeFiles = require __DIR__ . '/autoload_files.php';
        }
        foreach ($includeFiles as $fileIdentifier => $file) {
            require $file;
        }

        //最後一句
        return $loader;
    };
}

(三) 實現自動載入 驅動:/composer/ClassLoader.php

ClassLoader.php >
class ClassLoader{
    /**
     * 將此例項註冊為自動載入程式
     * -------------------------------------------
     * new App/User();時將 'App/User'類命傳遞給 loadClass('App/User')方法
     * 第二引數true debug
     * $prepend為true,將 loadClass() 放在自動載入函式棧的第一個,即優先處理。
     */
    public function register($prepend = false)
    {
        spl_autoload_register(array($this, 'loadClass'), true, $prepend);
    }

    /**
    * 載入給定的類或介面
    */
    public function loadClass($class)
    {
        if ($file = $this->findFile($class)) {
            includeFile($file);
            return true;
        }
    }

    /**
    * 查詢定義類的檔案的路徑
    * (二)類檔案提取 已將所有對映注入到此類中的陣列屬性中
    * 從陣列中根據 Key 值取出
    * 但並不是那麼簡單,本人沒有太過研究
    */
    public function findFile($class){
        //省略程式碼
        return $file;
    }
}

回顧

(一)類檔案歸納 驅動:composer
(二) 類檔案提取 驅動:/composer/autoload_real.php
(三) 實現自動載入 驅動:/composer/ClassLoader.php

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章