composer包的自动加载流程

PHP技术
421
0
0
2022-04-11
标签   Composer

在没有安装任何包的时候

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;
    }