composer的自动加载机制解读

按照composer文档的说法,如果是composer项目,只需要在开始的时候require 'vendor/autoload.php'即可享受类的自动加载特性。可是这是如何实现的呢?

vendor/autoload.php

以Laravel 5.1项目为例,vendor/autoload.php文件只做了两件事情:

  1. include vendor/composer/autoload_real.php
  2. 调用ComposerAutoloaderInitb6d254015e39cf5090fb84fdb1ed664b::getLoader()

vendor/composer/autoload_real.php仅仅定义了ComposerAutoloaderInitb6d254015e39cf5090fb84fdb1ed664b类和composerRequireb6d254015e39cf5090fb84fdb1ed664b函数。(b6d254015e39cf5090fb84fdb1ed664b应该是类似id一样的东西,确保每次不同)

接下来我们关注下ComposerAutoloaderInitb6d254015e39cf5090fb84fdb1ed664b::getLoader()做了哪些事情。

ComposerAutoloaderInitb6d254015e39cf5090fb84fdb1ed664b::getLoader()

首先,这个类的loader只会初始化一次,第二次是直接返回已经存在的loader了:

if (null !== self::$loader) {
    return self::$loader;
}

如果是第一次调用,先注册['ComposerAutoloaderInitb6d254015e39cf5090fb84fdb1ed664b', 'loadClassLoader'],然后new一个\Composer\Autoload\ClassLoader 作为loader,然后立马取消注册loadClassLoader

接下来就一步一步处理各种autoload了。

autoload_namespaces.php

直接从vendor/composer/autoload_namespaces.php中返回的就是$namespace$path的一个数组。通过\Composer\Autoload\ClassLoader::set方法来记录这些信息:

if (!$prefix) {
    // 如果namespace/prefix为空,则在其他PSR-0规则中查不到,才会到这个规则中
    $this->fallbackDirsPsr0 = (array) $paths;
} else {
    // 以后根据对应的namespace/prefix查找类的时候,第一个字母用来加速
    // 使用第一个字母作为一级key,可以减少二级key的冲突
    $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
}

autoload_psr4.php

直接从该文件的返回值是$namespace$path的数组。通过\Composer\Autoload\ClassLoader::setPsr4来记录此信息:

if (!$prefix) {
    // 如果namespace/prefix为空,则其他PSR-4规则查找不到的时候,会到此规则中查找
    $this->fallbackDirsPsr4 = (array) $paths;
} else {
    // TODO
    $length = strlen($prefix);
    // 略去错误检查

    // TODO
    $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
    // 用来根据类前缀找path
    $this->prefixDirsPsr4[$prefix] = (array) $paths;
}

autoload_classmap.php

直接从该文件的返回值是$classname$path的数组。通过\Composer\Autoload\ClassLoader::addClassMap来记录此信息:

if ($this->classMap) {
    $this->classMap = array_merge($this->classMap, $classMap);
} else {
    $this->classMap = $classMap;
}

这些都处理完之后,\Composer\Autoload\ClassLoader::register[$this, 'loadClass']注册为autoload函数(通过spl_autoload_register)。

autoload_files.php

直接从该文件的返回值是$fileIdentifier到文件路径的映射,通过之前定义的composerRequireb6d254015e39cf5090fb84fdb1ed664b函数来require每个文件。

这个函数在require文件的时候同时也设置了$GLOBALS['__composer_autoload_files'][$fileIdentifier]的值为true。

\Composer\Autoload\ClassLoader::loadClass

这个函数的作用是装载一个类。

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

    return true;
}

\Composer\Autoload\ClassLoader::findFile

这个函数是composer装载类的重点。

  1. 首先是从classMap里面找$class对应的文件,如果有,直接返回。
  2. 然后从prefixDirsPsr4找到前缀符合的文件,如果找到,直接返回。(那个prefixLengthsPsr4就是用来判断需要从$class去掉的前缀长度)
  3. 接下来,直接从fallbackDirsPsr4对应的目录中查找文件。
  4. PSR-0加载
    • namespaced class name变换规则:\A_namespace\a_class=>/A_namespace/a/class.php 
    • PEAR-like class name变换规则:\A_namespace\a_class=>/A/namespace/a/class.php 

    prefixesPsr0中查找(和prefixDirsPsr4类似),直接找到对应的文件,返回。

  5. 如果没有,直接从fallbackDirsPsr0中尝试加载。

Leave a Reply

Your email address will not be published. Required fields are marked *

20 − 11 =