再看 Composer 自动加载源码

composer 自动加载源码分析

  1. 请大家先看一下 你生产环境的 Composer 是这样吗? 中的自动加载部分
  2. 请大家先大概看一次源码在阅读该文章,这些都是我在看的过程产生的疑问一步一步查到,可以为大家节省一些时间。

自动加载规范

PSR 是由 PHP FIG 组织制定的 PHP 规范,是 PHP 开发的实践标准。Composer 自动加载规范也遵循该规则。

PSR-0 与 PSR-4 规范

PSR-0 与 PSR-4 区别

下化线区别

PSR-0 : 类名称中的每个 _ 字符也会被转换成文件夹路径分隔符,而命名空间中的 _ 字符则是无特殊含义的。

PSR-4 : 无等数含义

映射目录区别

"autoload": {
    "psr-0": {
        "Cx\\": "prs0/"
    },
    "psr-4": {
        "App\\": "app/"
    }
}

假设文件的根目录为 /path/to/project

规范 命名空间类 文件目录
PSR-0 Cx\My\Big_Dog /path/to/project/psr0/Cx/My/Big/Dog.php
PSR-4 App\My\Big_Dog /path/to/project/app/My/Big_Dog.php

目录

下面描述的是 vendor/composer 目录下的文件

文件 作用
autoload_classmap.php 所有命名空间包含的类(不一定会有,看是否执行了 composer dump-autoload -o) (类文件命名空间与文件位置的对应数组)
autoload_files.php 用于加载全局函数的文件(静态文件数组)
autoload_namespaces.php 符合 PSR0 标准的自动加载文件(PRS-0 命名空间 与位置对应数组)
autoload_psr4.php 符合 PSR4 标准的自动加载文件 (PRS-4 命名空间 与位置对应数组)
autoload_real.php 自动加载功能的引导类。(入口
autoload_static.php 顶级命名空间与文件路径对应 (辅助作用,给ClassLoader类属性初始化)
ClassLoader.php composer 加载类。(核心

源码解析

composer 自动加载总体来说就是两部分 初始化载入自动加载。下面我们具体分析下 (在此之前请了解下 sql_aotoload() 基本用法)

1. 初始化

目的就是为了给 ClassLoader 文件中的属性进行初始化。初始化 psr0、psr4,以及类 map 数组

# vim ./vendor/composer/ClassLoader.php :46

class ClassLoader
{
    // PSR-4
    // 该属性初始化各个composer文件中 PSR-4命名空间长度
    private $prefixLengthsPsr4 = array();
    // 该属性初始化各个composer文件中 PSR-4命名空间与路径的映射关系
    private $prefixDirsPsr4 = array();
    // 该属性初始化各个composer文件中 PSR-4命名空间为空与路径的映射关系
    private $fallbackDirsPsr4 = array();

    // PSR-0
    // 该属性初始化各个composer文件中 PSR-0命名空间与路径映射的关系
    private $prefixesPsr0 = array();
    // 该属性初始化各个composer文件中 PSR-0命名空间为空与路径的映射关系
    private $fallbackDirsPsr0 = array();

    // Composer 自动加载器也会到 PHP 的 include_path (PHP.INI)中查找
    private $useIncludePath = false;
    // 该属性初始化类与文件的位置的映射关系
    private $classMap = array();
    // 设置为true,Composer将只在classMap 中查找文件(适用于生产)
    private $classMapAuthoritative = false;
    // 保存不存在的类,以便下次require时不在进行磁盘加载(提高效率 ClassLoader.php:379)
    private $missingClasses = array();
    // PHP APCU拓展,用户缓存
    private $apcuPrefix;
}

2. 载入自动加载(初始化已执行完成)

载入 loadClass
# vim ./vendor/composer/ClassLoader.php :306

/**
 * 注册自动加载
 *
 * @parma $prepend bool 是否将loadClass 加入自动加载队列之首
 */
public function register($prepend = false)
{
    spl_autoload_register(array($this, 'loadClass'), true, $prepend);
}
loadClass 源码分析

loadClass 核心方法

# vim ./vendor/composer/ClassLoader.php :325

/**
 * 自动加载查找文件,查找到则包含
 *
 * @parma $class string 完全类名
 */
public function loadClass($class)
{
    if ($file = $this->findFile($class)) {
        includeFile($file);

        return true;
    }
}

findFile 查找文件

# vim ./vendor/composer/ClassLoader.php :341

/**
 * 通过完全类名查找文件
 * @param string $class 完全类名
 * @return string|false false为未查找到文件
 */
public function findFile($class) 
{
    // 先通过classMap 完美类名与路径对应关系直接查找
    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');

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

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

符合 PSR-0 与 PSR-4 规范查找文件

# vim ./vendor/composer/ClassLoader.php :376

/**
 * 按照 PSR-0 与 PSR-4 查找文件
 * @param string $class 完全类名
 * @param string $ext 文件后缀
 * @return string|false false为未查找到文件
 */
private function findFileWithExtension($class, $ext)
{
    /**
     * 1. 在 $this->prefixLengthsPsr4 查找该类的命名空间是否存在
     * 2. 在 $this->prefixDirsPsr4 查找该命名空间所对应的位置 
     * 3. 循环查找返回的位置,按照 PSR-4 规范组装文件路径,若文件存在则返回退出 
     */
    // PSR-4 lookup
    // 按照 PSR-4 直接将类名转化为文件
    $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;

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

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

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

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

    // PSR-0 include paths.
    /**
     * 使用了 useIncludePath,并且能查找到该文件
     */
    if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
        return $file;
    }

    return false;
}
本作品采用《CC 协议》,转载必须注明作者和本文链接
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!