MENU

#04: Composer 加载原理 —— 加载

January 21, 2020 • Laravel 源码

当我们使用了未知的类时就会触发执行 vendor/composer/ClassLoader.php 类实例中 loadClass ,最终就有它完成相应文件的引入:

// vendor/composer/ClassLoader.php
public function loadClass($class)
{
    if ($file = $this->findFile($class)) {
        includeFile($file);

        return true;
    }
}

流程很简单,看它如何查找文件:

public function findFile($class)
{
    //先看命名空间是否在映射数组中即可
    if (isset($this->classMap[$class])) {
        return $this->classMap[$class];
    }

    // 如果用户开发了 classMapAuthoritative 或者 这个类之前未查找到,则直接返回false
    if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
        return false;
    }

    // 如果开启了 apcu 缓存,则通过该拓展查询
    if (null !== $this->apcuPrefix) {
        $file = apcu_fetch($this->apcuPrefix.$class, $hit);
        if ($hit) {
            return $file;
        }
    }

    //通过 PRS-0 与 PSR-4 规范查找文件
    $file = $this->findFileWithExtension($class, '.php');

    // 未查找到通过,开启了HHVM,则在查找一次
    if (false === $file && defined('HHVM_VERSION')) {
        $file = $this->findFileWithExtension($class, '.hh');
    }

    // apcu 缓存查询结果
    if (null !== $this->apcuPrefix) {
        apcu_add($this->apcuPrefix.$class, $file);
    }

    // 未查找到就标记起来,下次就不需要重新查找了
    if (false === $file) {
        // Remember that this class does not exist.
        $this->missingClasses[$class] = true;
    }

    return $file;
}

过程已经在注释写好了,现在看 findFileWithExtension() 是如何通过 PSR-0、PSR-4 查找文件的:

private function findFileWithExtension($class, $ext)
{
    // PSR-4 lookup
    //按照 PSR-4 直接将类名转化为文件
    $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;

    /**
     * 1. 在 $this->prefixLengthsPsr4 中根据首字母查找该类的命名空间是否存在
     * 2. 在 $this->prefixDirsPsr4 中根据类名查找该命名空间所对应的位置 
     * 3. 循环查找返回的位置,按照 PSR-4 规范组装文件路径,若文件存在则返回退出 
     */    $first = $class[0];
    if (isset($this->prefixLengthsPsr4[$first])) {
        $subPath = $class;
        while (false !== $lastPos = strrpos($subPath, '\\')) {
            $subPath = substr($subPath, 0, $lastPos);
            $search = $subPath . '\\';
            if (isset($this->prefixDirsPsr4[$search])) {
                $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
                foreach ($this->prefixDirsPsr4[$search] as $dir) {
                    if (file_exists($file = $dir . $pathEnd)) {
                        return $file;
                    }
                }
            }
        }
    }

    // PSR-4 fallback dirs
    // 循环查找 $this->fallbackDirsPsr4 命名空间为空所对应的目录文件
    foreach ($this->fallbackDirsPsr4 as $dir) {
        if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
            return $file;
        }
    }

    // PSR-0 lookup
    /**
     * 1. 先按照 PRS-0 将命名空间转化成文件路径
     * 2. 在 $this->prefixesPsr0 查找该类的命名空间(首字母)是否存在
     * 3. 循环 $this->prefixesPsr0 中命名空间所对应位置,并按照 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
    //循环查找 $this->fallbackDirsPsr0 命名空间为空所对应的目录文件
    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;
    }

    return false;
}

当完成文件路径查找后,调用 include() 方法进行加载:

function includeFile($file)
{
    include $file;
}
Last Modified: January 29, 2020