composer包的自動載入流程

yaoxs 發表於 2021-05-03

在沒有安裝任何包的時候

vendor\composer\autoload_static.php裡面的ComposerStaticInitd599f67bc4dd02223ec31783a7f7938f類只有
public static $classMap和public static function getInitializer(ClassLoader $loader)兩個屬性或者方法
執行 composer require phpoffice/phpspreadsheet
ComposerStaticInitd599f67bc4dd02223ec31783a7f7938f 會增加
public static $files
public static $prefixLengthsPsr4(這裡儲存的是包的跟目錄(個人理解))
public static $prefixDirsPsr4(根據包的根目錄去拿到執行php檔案的根目錄)
public static $prefixesPsr0
和修改了public static function getInitializer方法(有些語法還不太理解暫時沒看,主要是講一些資料賦值給ClassLoader $loader)

使用composer載入的包

新建一個php檔案引入autoload.php
require_once DIR . ‘/vendor/autoload.php’;
呼叫這個類的功能
$spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet();

composer自動載入的執行流程

載入 /composer/autoload_real.php 檔案
呼叫 ComposerStaticInitd599f67bc4dd02223ec31783a7f7938f::getLoader();

getLoader方法執行流程

這裡只是單純的對使用composer包的載入php檔案進行理解,跳過其他載入過程
驗證PHP版本(platform_check.php)
self::$loader = $loader = new \Composer\Autoload\ClassLoader(實現了PSR-0,PSR-4和classmap類載入器)
載入 vendor\composer\autoload_static.php檔案(會驗證PHP版本和zend_loader_file_encoded方法,zend_loader_file_encoded存在應該是使用其他邏輯)
呼叫\Composer\Autoload\ComposerStaticInitd599f67bc4dd05423ece1783a7f7938f::getInitializer($loader)方法
getInitializer方法主要是賦值ComposerStaticInitd599f67bc4dd02223ec31783a7f7938f類中的$prefixLengthsPsr4、$prefixDirsPsr4等給ClassLoader物件
註冊自動載入器($loader->register(true);)
當建立phpoffice/phpspreadsheet包裡面的物件的時候,找不到這個類會呼叫註冊的自動載入器
loadClass($class) -> findFile($class) -> findFileWithExtension($class, ‘.php’)
loadClass 找到相關檔案後,引入(include)這個檔案
findFile 查詢定義類的檔案的路徑並返回檔案路徑
findFileWithExtension 根據$prefixLengthsPsr4、$prefixDirsPsr4來找到這個類 返回檔案路徑
這就是new \PhpOffice\PhpSpreadsheet\Spreadsheet() 引入Spreadsheet.php檔案的大致流程

composer包的關鍵程式碼和註釋

index.php

require_once __DIR__ . '/vendor/autoload.php';
$spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet();

autoload.php

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

composer/autoload_real.php部分程式碼

<?php
// autoload_real.php @generated by Composer
class ComposerStaticInitd599f67bc4dd02223ec31783a7f7938f{
    private static $loader;
    public static function getLoader(){ 
        if (null !== self::$loader) {
            return self::$loader;
        } 
        // 對PHP版本的驗證(version ">= 7.3.0")
        // __DIR__ 指向當前執行指令碼的目錄
        require __DIR__ . '/platform_check.php'; 
        // 類的自動載入
        spl_autoload_register(array('ComposerAutoloaderInitd599f67bc4dd05423ece1783a7f7938f', 'loadClassLoader'), true, true);
    // __FILE__   D:\....test\vendor\composer\autoload_real.php
    // \dirname(\dirname(__FILE__)) D:\phpstudy_pro\WWW\test\vendor  
    self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
    // 登出自動載入函式
    spl_autoload_unregister(array('ComposerAutoloaderInitd599f67bc4dd05423ece1783a7f7938f', 'loadClassLoader')); 
    // function_exists 判斷函式是否被定義
    $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
    if ($useStaticLoader) {
        require __DIR__ . '/autoload_static.php';
        // call_user_func把第一個引數作為回撥函式呼叫
        // 給$loader賦值一些prefixLengthsPsr
        call_user_func(
\Composer\Autoload\ComposerStaticInitd599f67bc4dd05423ece1783a7f7938f::getInitializer($loader)
        );
    } else {
        // 個人覺得這裡應該是主要判斷zend_loader_file_encoded有被定義的模型
    }
    // 註冊自動載入器。
    $loader->register(true);
    // 略       
    return $loader; }}

new \PhpOffice\PhpSpreadsheet\Spreadsheet() 引入Spreadsheet.php檔案的大致流程

ClassLoader.php

<?php
namespace Composer\Autoload;
class ClassLoader{
    private $vendorDir;
    // PSR-4 
    private $prefixLengthsPsr4 = array();
    private $prefixDirsPsr4 = array();
    // 省略掉不相關的一些方法
    // 類的自動載入入口
    public function loadClass($class){
        if ($file = $this->findFile($class)) {
            // 最後載入Spreadsheet.php檔案
             includeFile($file);
             return true;
     }}
     // 查詢定義類的檔案的路徑。
     public function findFile($class){ 
        // 省略不相關的if判斷
        // composer 包走以下流程
         $file = $this->findFileWithExtension($class, '.php');
         // 省略不相關的if判斷
         // 返回檔案路徑
        return $file;
    }
     private function findFileWithExtension($class, $ext){ 
        // new \PhpOffice\PhpSpreadsheet\Spreadsheet(); 
        // $class 等於 PhpOffice\PhpSpreadsheet\Spreadsheet 
        // $ext = '.php' 
        // PSR-4 lookup 
        // strtr字串替換
        // 用DIRECTORY_SEPARATOR 來代替 \ 
        $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
        $first = $class[0];
        // 根據首字母去查詢包的根目錄
        // 如果是查詢composer包的引用檔案,感覺這裡的$this->prefixLengthsPsr4感覺作用不大
        // $this->prefixLengthsPsr4應該是其他地方有用到吧
        if (isset($this->prefixLengthsPsr4[$first])) {
             $subPath = $class;
             // strrpos 查詢字串最後一次出現的位置
             while (false !== $lastPos = strrpos($subPath, '\\')) { 
                // 拿到包的根目錄 $search = "PhpOffice\PhpSpreadsheet\" 
                $subPath = substr($subPath, 0, $lastPos);
                $search = $subPath . '\\';
                if (isset($this->prefixDirsPsr4[$search])) {
                    // $pathEnd = '\Spreadsheet.php' 
                    $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
                    foreach ($this->prefixDirsPsr4[$search] as $dir) {
                        // 判斷檔案(D:\...\test\vendor\composer/../phpoffice/phpspreadsheet/src/PhpSpreadsheet\Spreadsheet.php)是否存在
                        if (file_exists($file = $dir . $pathEnd)) {
                            // 存在返回檔案
                             return $file; 
                        }
                    }
                }
            }
        }
        // 略
        return false;
    }}
    function includeFile($file){
        include $file;
    }
本作品採用《CC 協議》,轉載必須註明作者和本文連結