溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

composer autoload源碼分析

發(fā)布時間:2020-09-11 11:04:31 來源:網(wǎng)絡(luò) 閱讀:1744 作者:china_lx1 欄目:web開發(fā)

現(xiàn)在很多主流框架都用到了composer,包管理實(shí)在是方便?,F(xiàn)在我就以yii2來舉例追蹤一遍composer autoload流程

第一步上yii2的web/index.php(入口文件)

<?php
defined('YII_DEBUG') or define('YII_DEBUG', true);
defined('YII_ENV') or define('YII_ENV', 'dev');

require __DIR__ . '/../vendor/autoload.php';    
require __DIR__ . '/../vendor/yiisoft/yii2/Yii.php';

$config = require __DIR__ . '/../config/web.php';

(new yii\web\Application($config))->run();


看到第五行,有引入vendor/autoload.php,于是我打開這個文件:

<?php

// autoload.php @generated by Composer

require_once __DIR__ . '/composer/autoload_real.php';

return ComposerAutoloaderInitc9edea7c33c2cce60a5a807424f71d3e::getLoader();


打開這個vendor/composer/autoload_real.php,找到ComposerAutoloaderInitc9edea7c33c2cce60a5a807424f71d3e這個類的getLoader方法

public static function getLoader()
{
    if (null !== self::$loader) {
        return self::$loader;
    }

    spl_autoload_register(array('ComposerAutoloaderInitc9edea7c33c2cce60a5a807424f71d3e', 'loadClassLoader'), true, true);
    self::$loader = $loader = new \Composer\Autoload\ClassLoader();
    spl_autoload_unregister(array('ComposerAutoloaderInitc9edea7c33c2cce60a5a807424f71d3e', 'loadClassLoader'));

    //psr0  分析見解釋1-1
    $map = require __DIR__ . '/autoload_namespaces.php';
    foreach ($map as $namespace => $path) {
        $loader->set($namespace, $path);
    }
    
    //psr4(這項(xiàng)最重要)    //分析見解釋1-2
    $map = require __DIR__ . '/autoload_psr4.php'; 
    foreach ($map as $namespace => $path) {
        $loader->setPsr4($namespace, $path);    
    }
    
    //分析見解釋1-3
    $classMap = require __DIR__ . '/autoload_classmap.php';
    if ($classMap) {
        $loader->addClassMap($classMap);
    }
    
    $loader->register(true);    //分析見解釋1-4
    
    //分析見解釋1-5
    $includeFiles = require __DIR__ . '/autoload_files.php';
    foreach ($includeFiles as $fileIdentifier => $file) {
        composerRequired991c576f7683adde3c22cdd0fe508de($fileIdentifier, $file);
    }
    
    return $loader;
}


解釋1-1

首先來看看這個autoload_namespaces.php

<?php

// autoload_namespaces.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

return array(
   'Imagine' => array($vendorDir . '/imagine/imagine/lib'),
    'HTMLPurifier' => array($vendorDir . '/ezyang/htmlpurifier/library'),
    'Diff' => array($vendorDir . '/phpspec/php-diff/lib'),
);

很明顯僅僅返回一個數(shù)組

再看看這語句含義

$loader->set($namespace, $path);

追蹤進(jìn)去,調(diào)用的是ClassLoader的set方法:

public function set($prefix, $paths)    
//以$prefix='Imagine', $paths = array($vendorDir . '/imagine/imagine/lib')來舉例
{
    if (!$prefix) {
        $this->fallbackDirsPsr0 = (array) $paths;
    } else {
        $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
        //得到$this->prefixesPsr0['I']['Imagine'] = array($vendorDir . '/imagine/imagine/lib')
    }
}


解釋1-2

打開vendor/composer/autoload_psr4.php

<?php

// autoload_psr4.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

return array(
    'yii\\swiftmailer\\' => array($vendorDir . '/yiisoft/yii2-swiftmailer'),
    'yii\\redis\\' => array($vendorDir . '/yiisoft/yii2-redis'),
    'yii\\mongodb\\' => array($vendorDir . '/yiisoft/yii2-mongodb'),
);

這個文件看過來,發(fā)現(xiàn)和psr0的namespace.php很像,只是這里加了命名空間

和psr0一樣,我們來看看這行

$loader->setPsr4($namespace, $path);

打開ClassLoader的setPsr4方法

public function setPsr4($prefix, $paths)
//以$prefix='yii\\mongodb\\', $paths = array($vendorDir . '/yiisoft/yii2-mongodb')來舉例
{
    if (!$prefix) {
        $this->fallbackDirsPsr4 = (array) $paths;
    } else {
            //字符串'yii\\mongodb\\'長度為12(注意這里長度計(jì)算實(shí)際為yii\mongodb\)
        $length = strlen($prefix);            
        if ('\\' !== $prefix[$length - 1]) {
            throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
        }
        //$this->prefixLengthsPsr4['y']['yii\\mongodb\\'] = 12;
        $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
        //$this->prefixLengthsPsr4['y']['yii\\mongodb\\'] = array($vendorDir . '/yiisoft/yii2-mongodb')
        $this->prefixDirsPsr4[$prefix] = (array) $paths;    
    }
}


解釋1-3(類名和類文件具體路徑直接映射表)

打開autoload_classmap.php

<?php

// autoload_classmap.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

return array(
    'File_Iterator' => $vendorDir . '/phpunit/php-file-iterator/src/Iterator.php',
    'File_Iterator_Facade' => $vendorDir . '/phpunit/php-file-iterator/src/Facade.php',
    'File_Iterator_Factory' => $vendorDir . '/phpunit/php-file-iterator/src/Factory.php',
);

經(jīng)過上面分析,現(xiàn)在我們直接來看ClassLoader的addClassMap方法:

public function addClassMap(array $classMap)
{
    if ($this->classMap) {
        $this->classMap = array_merge($this->classMap, $classMap);
    } else {
        $this->classMap = $classMap;
    }
}

/*
方法執(zhí)行完
$this->classMap = [
    'File_Iterator' => $vendorDir . '/phpunit/php-file-iterator/src/Iterator.php',
    'File_Iterator_Facade' => $vendorDir . '/phpunit/php-file-iterator/src/Facade.php',
    'File_Iterator_Factory' => $vendorDir . '/phpunit/php-file-iterator/src/Factory.php',
];
*/


解釋1-4

關(guān)鍵代碼$loader->register(true);

打開loader的register方法

/**
 * Registers this instance as an autoloader.
 *
 * @param bool $prepend Whether to prepend the autoloader or not
 */
public function register($prepend = false)
{
    spl_autoload_register(array($this, 'loadClass'), true, $prepend);
}

看完實(shí)際上就是通過當(dāng)前類的loadClass注冊了,假如new yii\mongodb\Connection找不到的時候就會調(diào)用ClassLoader的loadClass方法,于是我們看看loadClass方法

if ($file = $this->findFile($class)) {
    includeFile($file);

    return true;
}

看完繼續(xù)打開這個findFile方法

public function findFile($class)
{
    // work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731
    if ('\\' == $class[0]) {
        $class = substr($class, 1);
    }

    // 這里解釋1-3解釋過了
    if (isset($this->classMap[$class])) {
        return $this->classMap[$class];
    }
    //$this->classMapAuthoritative默認(rèn)為false
    if ($this->classMapAuthoritative) {
        return false;
    }

    $file = $this->findFileWithExtension($class, '.php');

    // 如果運(yùn)行在HHVM
    if ($file === null && defined('HHVM_VERSION')) {
        $file = $this->findFileWithExtension($class, '.hh');
    }

    if ($file === null) {
        //記住這個類不存在
        return $this->classMap[$class] = false;
    }

    return $file;
}

這個方法都好理解,假如是new yii\mongodb\Connection肯定會進(jìn)入到這行

$file = $this->findFileWithExtension($class, '.php');    //實(shí)際上$this->findFileWithExtension('yii\\mongodb\\Connection.php')

于是我們繼續(xù)讀findFileWithExtension方法

private function findFileWithExtension($class, $ext)    //舉例$class = 'yii\\mongodb\\Connection', $ext = '.php'
{
    // PSR-4尋找
    //會得到$logicalPathPsr4 = 'yii/mongodb/Connection.php';
    $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
    $first = $class[0];
    //由于我們之前setPsr4設(shè)置過此數(shù)組于是找到了
    if (isset($this->prefixLengthsPsr4[$first])) {    
            //$length 為字符串'yii\\mongodb\\'長度
        foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) {    
            if (0 === strpos($class, $prefix)) {
                foreach ($this->prefixDirsPsr4[$prefix] as $dir) {
                    //$dir為autoload_psr4.php映射的路徑,substr($logicalPathPsr4, $length)
                    //剛好可以得到Connection.php
                    //最終得到$file完整路徑
                    if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) {
                        return $file;
                    }
                }
            }
        }
    }

    // PSR-4 fallback dirs
    foreach ($this->fallbackDirsPsr4 as $dir) {
        if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
            return $file;
        }
    }

    // PSR-0同理
    if (false !== $pos = strrpos($class, '\\')) {
        // namespaced class name
        $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
            . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
    } else {
        // PEAR-like class name
        $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
    }

    if (isset($this->prefixesPsr0[$first])) {
        foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
            if (0 === strpos($class, $prefix)) {
                foreach ($dirs as $dir) {
                    if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
                        return $file;
                    }
                }
            }
        }
    }

    // PSR-0 fallback dirs
    foreach ($this->fallbackDirsPsr0 as $dir) {
        if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
            return $file;
        }
    }

    // PSR-0 include paths.
    if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
        return $file;
    }
}

通過這個方法找到類文件的完整路徑,最后只需要include($file)即可,到此composer autoload自動加載基本完畢


解釋1-5

我們還是先看看autoload_files.php文件是什么鬼

<?php

// autoload_files.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

return array(
    '2cffec82183ee1cea088009cef9a6fc3' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier.composer.php',
    '2c102faa651ef8ea5874edb585946bce' => $vendorDir . '/swiftmailer/swiftmailer/lib/swift_required.php',
);

再分析調(diào)用語句

composerRequired991c576f7683adde3c22cdd0fe508de($fileIdentifier, $file);

再看看這個方法:

function composerRequired991c576f7683adde3c22cdd0fe508de($fileIdentifier, $file)    
/*
$fileIdentifier = '2cffec82183ee1cea088009cef9a6fc3'
$file = $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier.composer.php'
*/
{
    if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
        require $file;

        $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
    }
}


到此,整個composer autoload流程都分析完畢,小弟才疏學(xué)淺,若有哪里不足,歡迎指正!!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI