文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

composer中自动加载原理的示例分析

2023-06-14 18:22

关注

这篇文章主要介绍了composer中自动加载原理的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。

前言

PHP 自5.3的版本之后,已经重焕新生,命名空间、性状(trait)、闭包、接口、PSR 规范、以及 composer 的出现已经让 PHP 变成了一门现代化的脚本语言。PHP 的生态系统也一直在演进,而 composer 的出现更是彻底的改变了以往构建 PHP 应用的方式,我们可以根据 PHP 的应用需求混合搭配最合适的 PHP 组件。当然这也得益于 PSR 规范的提出。


大纲


一、PHP 自动加载功能

PHP 自动加载功能的由来

在 PHP 开发过程中,如果希望从外部引入一个 Class ,通常会使用 includerequire 方法,去把定义这个 Class 的文件包含进来。这个在小规模开发的时候,没什么大问题。但在大型的开发项目中,使用这种方式会带来一些隐含的问题:如果一个 PHP 文件需要使用很多其它类,那么就需要很多的 require/include 语句,这样有可能会 造成遗漏 或者 包含进不必要的类文件。如果大量的文件都需要使用其它的类,那么要保证每个文件都包含正确的类文件肯定是一个噩梦, 况且 require或 incloud 的性能代价很大。

PHP5 为这个问题提供了一个解决方案,这就是 类的自动加载(autoload)机制autoload机制 可以使得 PHP 程序有可能在使用类时才自动包含类文件,而不是一开始就将所有的类文件include进来,这种机制也称为 Lazy loading (惰性加载)

PHP 自动加载函数 __autoload()

__autoload() 函数存在的问题

SPL Autoload

<?php// __autoload 函数//// function __autoload($class) {//     include 'classes/' . $class . '.class.php';// }function my_autoloader($class) {    include 'classes/' . $class . '.class.php';}spl_autoload_register('my_autoloader');// 定义的 autoload 函数在 class 里// 静态方法class MyClass {  public static function autoload($className) {    // ...  }}spl_autoload_register(array('MyClass', 'autoload'));// 非静态方法class MyClass {  public function autoload($className) {    // ...  }}$instance = new MyClass();spl_autoload_register(array($instance, 'autoload'));

spl_autoload_register() 就是我们上面所说的__autoload调用堆栈,我们可以向这个函数注册多个我们自己的 autoload() 函数,当 PHP 找不到类名时,PHP就会调用这个堆栈,然后去调用自定义的 autoload() 函数,实现自动加载功能。如果我们不向这个函数输入任何参数,那么就会默认注册 spl_autoload() 函数。


二、PSR 规范

与自动加载相关的规范是 PSR4,在说 PSR4 之前先介绍一下 PSR 标准。PSR 标准的发明和推出组织是:PHP-FIG,它的网站是:www.php-fig.org。由几位开源框架的开发者成立于 2009 年,从那开始也选取了很多其他成员进来,虽然不是 “官方” 组织,但也代表了社区中不小的一块。组织的目的在于:以最低程度的限制,来统一各个项目的编码规范,避免各家自行发展的风格阻碍了程序员开发的困扰,于是大伙发明和总结了 PSR,PSR 是 PHP Standards Recommendation 的缩写,截止到目前为止,总共有 14 套 PSR 规范,其中有 7 套PSR规范已通过表决并推出使用,分别是:

PSR-0 自动加载标准(已废弃,一些旧的第三方库还有在使用)

PSR-1 基础编码标准

PSR-2 编码风格向导

PSR-3 日志接口

PSR-4 自动加载的增强版,替换掉了 PSR-0

PSR-6 缓存接口规范

PSR-7 HTTP 消息接口规范

具体详细的规范标准可以查看PHP 标准规范

PSR4 标准

2013 年底,PHP-FIG 推出了第 5 个规范——PSR-4。

PSR-4 规范了如何指定文件路径从而自动加载类定义,同时规范了自动加载文件的位置。

1)一个完整的类名需具有以下结构:

\<命名空间>\<子命名空间>\<类名>

2)根据完整的类名载入相应的文件
3) 例子

PSR-4风格

类名:ZendAbc    
命名空间前缀:Zend    
文件基目录:/usr/includes/Zend/    
文件路径:/usr/includes/Zend/Abc.php
类名:SymfonyCoreRequest  
命名空间前缀:SymfonyCore      
文件基目录:./vendor/Symfony/Core/  
文件路径:./vendor/Symfony/Core/Request.php

目录结构

-vendor/| -vendor_name/| | -package_name/| | | -src/| | | | -ClassName.php       # Vendor_Name\Package_Name\ClassName| | | -tests/| | | | -ClassNameTest.php   # Vendor_Name\Package_Name\ClassNameTest

Composer自动加载过程

Composer 做了哪些事情

例如,你正在创建一个项目,需要做一些单元测试。你决定使用 phpunit 。为了将它添加到你的项目中,你所需要做的就是在 composer.json 文件里描述项目的依赖关系。

 {   "require": {     "phpunit/phpunit":"~6.0",   } }

然后在 composer require 之后我们只要在项目里面直接 use phpunit 的类即可使用。

执行 composer require 时发生了什么

(如:'PHPUnit\\Framework\\Assert' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Assert.php'

题外话:现在很多框架都已经帮我们写好了顶级域名映射了,我们只需要在框架里面新建文件,在新建的文件中写好命名空间,就可以在任何地方 use 我们的命名空间了。


Composer 源码分析

下面我们通过对源码的分析来看看 composer 是如何实现 PSR4标准 的自动加载功能。

很多框架在初始化的时候都会引入 composer 来协助自动加载的,以 Laravel 为例,它入口文件 index.php 第一句就是利用 composer 来实现自动加载功能。

启动

<?php  define('LARAVEL_START', microtime(true));  require __DIR__ . '/../vendor/autoload.php';

去 vendor 目录下的 autoload.php

<?php  require_once __DIR__ . '/composer' . '/autoload_real.php';  return ComposerAutoloaderInit7b790917ce8899df9af8ed53631a1c29::getLoader();

这里就是 Composer 真正开始的地方了

Composer自动加载文件

首先,我们先大致了解一下Composer自动加载所用到的源文件。

  1. autoload_real.php: 自动加载功能的引导类。

    • composer 加载类的初始化(顶级命名空间与文件路径映射初始化)和注册(spl_autoload_register())。

  2. ClassLoader.php : composer 加载类。

    • composer 自动加载功能的核心类。

  3. autoload_static.php : 顶级命名空间初始化类,

    • 用于给核心类初始化顶级命名空间。

  4. autoload_classmap.php : 自动加载的最简单形式,

    • 有完整的命名空间和文件目录的映射;

  5. autoload_files.php : 用于加载全局函数的文件,

    • 存放各个全局函数所在的文件路径名;

  6. autoload_namespaces.php : 符合 PSR0 标准的自动加载文件,

    • 存放着顶级命名空间与文件的映射;

  7. autoload_psr4.php : 符合 PSR4 标准的自动加载文件,

    • 存放着顶级命名空间与文件的映射;

autoload_real 引导类


在 vendor 目录下的 autoload.php 文件中我们可以看出,程序主要调用了引导类的静态方法 getLoader() ,我们接着看看这个函数。

<?php    public static function getLoader()    {      if (null !== self::$loader) {          return self::$loader;      }      spl_autoload_register(        array('ComposerAutoloaderInit7b790917ce8899df9af8ed53631a1c29', 'loadClassLoader'), true, true      );      self::$loader = $loader = new \Composer\Autoload\ClassLoader();      spl_autoload_unregister(        array('ComposerAutoloaderInit7b790917ce8899df9af8ed53631a1c29', 'loadClassLoader')      );      $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION');      if ($useStaticLoader) {          require_once __DIR__ . '/autoload_static.php';          call_user_func(          \Composer\Autoload\ComposerStaticInit7b790917ce8899df9af8ed53631a1c29::getInitializer($loader)          );      } else {          $map = require __DIR__ . '/autoload_namespaces.php';          foreach ($map as $namespace => $path) {              $loader->set($namespace, $path);          }          $map = require __DIR__ . '/autoload_psr4.php';          foreach ($map as $namespace => $path) {              $loader->setPsr4($namespace, $path);          }          $classMap = require __DIR__ . '/autoload_classmap.php';          if ($classMap) {              $loader->addClassMap($classMap);          }      }            $loader->register(true);            if ($useStaticLoader) {          $includeFiles = Composer\Autoload\ComposerStaticInit7b790917ce8899df9af8ed53631a1c29::$files;      } else {          $includeFiles = require __DIR__ . '/autoload_files.php';      }      foreach ($includeFiles as $fileIdentifier => $file) {          composerRequire7b790917ce8899df9af8ed53631a1c29($fileIdentifier, $file);      }      return $loader;    }

我把自动加载引导类分为 5 个部分。

第一部分——单例

第一部分很简单,就是个最经典的单例模式,自动加载类只能有一个。

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

第二部分——构造ClassLoader核心类

第二部分 new 一个自动加载的核心类对象。

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

loadClassLoader()函数:

<?phppublic static function loadClassLoader($class){    if ('Composer\Autoload\ClassLoader' === $class) {        require __DIR__ . '/ClassLoader.php';    }}

从程序里面我们可以看出,composer 先向 PHP 自动加载机制注册了一个函数,这个函数 require 了 ClassLoader 文件。成功 new 出该文件中核心类 ClassLoader() 后,又销毁了该函数。

第三部分 —— 初始化核心类对象

<?php    $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION');  if ($useStaticLoader) {     require_once __DIR__ . '/autoload_static.php';     call_user_func(       \Composer\Autoload\ComposerStaticInit7b790917ce8899df9af8ed53631a1c29::getInitializer($loader)     );  } else {      $map = require __DIR__ . '/autoload_namespaces.php';      foreach ($map as $namespace => $path) {         $loader->set($namespace, $path);      }      $map = require __DIR__ . '/autoload_psr4.php';      foreach ($map as $namespace => $path) {         $loader->setPsr4($namespace, $path);      }      $classMap = require __DIR__ . '/autoload_classmap.php';      if ($classMap) {          $loader->addClassMap($classMap);      }    }

这一部分就是对自动加载类的初始化,主要是给自动加载核心类初始化顶级命名空间映射。

初始化的方法有两种:

  1. 使用 autoload_static 进行静态初始化;  2. 调用核心类接口初始化。

autoload_static 静态初始化 ( PHP >= 5.6 )

静态初始化只支持 PHP5.6 以上版本并且不支持 HHVM 虚拟机。我们深入 autoload_static.php 这个文件发现这个文件定义了一个用于静态初始化的类,名字叫 ComposerStaticInit7b790917ce8899df9af8ed53631a1c29,仍然为了避免冲突而加了 hash 值。这个类很简单:

<?php  class ComposerStaticInit7b790917ce8899df9af8ed53631a1c29{     public static $files = array(...);     public static $prefixLengthsPsr4 = array(...);     public static $prefixDirsPsr4 = array(...);     public static $prefixesPsr0 = array(...);     public static $classMap = array (...);    public static function getInitializer(ClassLoader $loader)    {      return \Closure::bind(function () use ($loader) {          $loader->prefixLengthsPsr4                          = ComposerStaticInit7b790917ce8899df9af8ed53631a1c29::$prefixLengthsPsr4;          $loader->prefixDirsPsr4                          = ComposerStaticInit7b790917ce8899df9af8ed53631a1c29::$prefixDirsPsr4;          $loader->prefixesPsr0                          = ComposerStaticInit7b790917ce8899df9af8ed53631a1c29::$prefixesPsr0;          $loader->classMap                          = ComposerStaticInit7b790917ce8899df9af8ed53631a1c29::$classMap;      }, null, ClassLoader::class);  }

这个静态初始化类的核心就是 getInitializer() 函数,它将自己类中的顶级命名空间映射给了 ClassLoader 类。值得注意的是这个函数返回的是一个匿名函数,为什么呢?原因就是 ClassLoader类 中的 prefixLengthsPsr4prefixDirsPsr4等等变量都是 private的。利用匿名函数的绑定功能就可以将这些 private 变量赋给 ClassLoader 类 里的成员变量。

关于匿名函数的绑定功能。

接下来就是命名空间初始化的关键了。

classMap(命名空间映射)
<?php  public static $classMap = array (      'App\\Console\\Kernel'              => __DIR__ . '/../..' . '/app/Console/Kernel.php',      'App\\Exceptions\\Handler'              => __DIR__ . '/../..' . '/app/Exceptions/Handler.php',      'App\\Http\\Controllers\\Auth\\ForgotPasswordController'              => __DIR__ . '/../..' . '/app/Http/Controllers/Auth/ForgotPasswordController.php',      'App\\Http\\Controllers\\Auth\\LoginController'              => __DIR__ . '/../..' . '/app/Http/Controllers/Auth/LoginController.php',      'App\\Http\\Controllers\\Auth\\RegisterController'              => __DIR__ . '/../..' . '/app/Http/Controllers/Auth/RegisterController.php',  ...)

直接命名空间全名与目录的映射,简单粗暴,也导致这个数组相当的大。

PSR4 标准顶级命名空间映射数组:
<?php  public static $prefixLengthsPsr4 = array(      'p' => array (        'phpDocumentor\\Reflection\\' => 25,    ),      'S' => array (        'Symfony\\Polyfill\\Mbstring\\' => 26,        'Symfony\\Component\\Yaml\\' => 23,        'Symfony\\Component\\VarDumper\\' => 28,        ...    ),  ...);  public static $prefixDirsPsr4 = array (      'phpDocumentor\\Reflection\\' => array (        0 => __DIR__ . '/..' . '/phpdocumentor/reflection-common/src',        1 => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src',        2 => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src',    ),       'Symfony\\Polyfill\\Mbstring\\' => array (        0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring',    ),      'Symfony\\Component\\Yaml\\' => array (        0 => __DIR__ . '/..' . '/symfony/yaml',    ),  ...)

PSR4 标准顶级命名空间映射用了两个数组,第一个是用命名空间第一个字母作为前缀索引,然后是 顶级命名空间,但是最终并不是文件路径,而是 顶级命名空间的长度。为什么呢?

因为 PSR4 标准是用顶级命名空间目录替换顶级命名空间,所以获得顶级命名空间的长度很重要。

具体说明这些数组的作用:

假如我们找 Symfony\Polyfill\Mbstring\example 这个命名空间,通过前缀索引和字符串匹配我们得到了

<?php    'Symfony\\Polyfill\\Mbstring\\' => 26,

这条记录,键是顶级命名空间,值是命名空间的长度。拿到顶级命名空间后去 $prefixDirsPsr4数组 获取它的映射目录数组:(注意映射目录可能不止一条)

<?php  'Symfony\\Polyfill\\Mbstring\\' => array (              0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring',          )

然后我们就可以将命名空间 Symfony\\Polyfill\\Mbstring\\example 前26个字符替换成目录 __DIR__ . '/..' . '/symfony/polyfill-mbstring ,我们就得到了__DIR__ . '/..' . '/symfony/polyfill-mbstring/example.php,先验证磁盘上这个文件是否存在,如果不存在接着遍历。如果遍历后没有找到,则加载失败。

ClassLoader 接口初始化( PHP < 5.6 )


如果PHP版本低于 5.6 或者使用 HHVM 虚拟机环境,那么就要使用核心类的接口进行初始化。

<?php    // PSR0 标准    $map = require __DIR__ . '/autoload_namespaces.php';    foreach ($map as $namespace => $path) {       $loader->set($namespace, $path);    }    // PSR4 标准    $map = require __DIR__ . '/autoload_psr4.php';    foreach ($map as $namespace => $path) {       $loader->setPsr4($namespace, $path);    }    $classMap = require __DIR__ . '/autoload_classmap.php';    if ($classMap) {       $loader->addClassMap($classMap);    }
PSR4 标准的映射

autoload_psr4.php 的顶级命名空间映射

<?php    return array(    'XdgBaseDir\\'        => array($vendorDir . '/dnoegel/php-xdg-base-dir/src'),    'Webmozart\\Assert\\'        => array($vendorDir . '/webmozart/assert/src'),    'TijsVerkoyen\\CssToInlineStyles\\'        => array($vendorDir . '/tijsverkoyen/css-to-inline-styles/src'),    'Tests\\'        => array($baseDir . '/tests'),    'Symfony\\Polyfill\\Mbstring\\'        => array($vendorDir . '/symfony/polyfill-mbstring'),    ...    )

PSR4 标准的初始化接口:

<?php    public function setPsr4($prefix, $paths)    {        if (!$prefix) {            $this->fallbackDirsPsr4 = (array) $paths;        } else {            $length = strlen($prefix);            if ('\\' !== $prefix[$length - 1]) {                throw new \InvalidArgumentException(                  "A non-empty PSR-4 prefix must end with a namespace separator."                );            }            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;            $this->prefixDirsPsr4[$prefix] = (array) $paths;        }    }

总结下上面的顶级命名空间映射过程:

( 前缀 -> 顶级命名空间,顶级命名空间 -> 顶级命名空间长度 )( 顶级命名空间 -> 目录 )

这两个映射数组。具体形式也可以查看下面的 autoload_static 的 $prefixLengthsPsr4 、 $prefixDirsPsr4 。

命名空间映射

autoload_classmap:

<?phppublic static $classMap = array (    'App\\Console\\Kernel'        => __DIR__ . '/../..' . '/app/Console/Kernel.php',    'App\\Exceptions\\Handler'        => __DIR__ . '/../..' . '/app/Exceptions/Handler.php',    ...)

addClassMap:

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

自动加载核心类 ClassLoader 的静态初始化到这里就完成了!

其实说是5部分,真正重要的就两部分——初始化与注册。初始化负责顶层命名空间的目录映射,注册负责实现顶层以下的命名空间映射规则。

第四部分 —— 注册


讲完了 Composer 自动加载功能的启动与初始化,经过启动与初始化,自动加载核心类对象已经获得了顶级命名空间与相应目录的映射,也就是说,如果有命名空间 'App\Console\Kernel,我们已经可以找到它对应的类文件所在位置。那么,它是什么时候被触发去找的呢?

这就是 composer 自动加载的核心了,我们先回顾一下自动加载引导类:

 public static function getLoader() {        if (null !== self::$loader) {        return self::$loader;    }            spl_autoload_register(array('ComposerAutoloaderInit    7b790917ce8899df9af8ed53631a1c29', 'loadClassLoader'), true, true);        self::$loader = $loader = new \Composer\Autoload\ClassLoader();        spl_autoload_unregister(array('ComposerAutoloaderInit    7b790917ce8899df9af8ed53631a1c29', 'loadClassLoader'));        $useStaticLoader = PHP_VERSION_ID >= 50600 &&     !defined('HHVM_VERSION');        if ($useStaticLoader) {        require_once __DIR__ . '/autoload_static.php';        call_user_func(\Composer\Autoload\ComposerStaticInit        7b790917ce8899df9af8ed53631a1c29::getInitializer($loader));      } else {        $map = require __DIR__ . '/autoload_namespaces.php';        foreach ($map as $namespace => $path) {            $loader->set($namespace, $path);        }        $map = require __DIR__ . '/autoload_psr4.php';        foreach ($map as $namespace => $path) {            $loader->setPsr4($namespace, $path);        }        $classMap = require __DIR__ . '/autoload_classmap.php';        if ($classMap) {            $loader->addClassMap($classMap);        }    }        $loader->register(true);        if ($useStaticLoader) {        $includeFiles = Composer\Autoload\ComposerStaticInit        7b790917ce8899df9af8ed53631a1c29::$files;    } else {        $includeFiles = require __DIR__ . '/autoload_files.php';    }        foreach ($includeFiles as $fileIdentifier => $file) {        composerRequire        7b790917ce8899df9af8ed53631a1c29($fileIdentifier, $file);    }    return $loader;}

现在我们开始引导类的第四部分:注册自动加载核心类对象。我们来看看核心类的 register() 函数:

public function register($prepend = false){    spl_autoload_register(array($this, 'loadClass'), true, $prepend);}

其实奥秘都在自动加载核心类 ClassLoader 的 loadClass() 函数上:

public function loadClass($class)    {        if ($file = $this->findFile($class)) {            includeFile($file);            return true;        }    }

这个函数负责按照 PSR 标准将顶层命名空间以下的内容转为对应的目录,也就是上面所说的将  'App\Console\Kernel 中' Console\Kernel 这一段转为目录,至于怎么转的在下面 “运行”的部分讲。核心类 ClassLoader 将 loadClass() 函数注册到PHP SPL中的 spl_autoload_register() 里面去。这样,每当PHP遇到一个不认识的命名空间的时候,PHP会自动调用注册到 spl_autoload_register 里面的 loadClass() 函数,然后找到命名空间对应的文件。

全局函数的自动加载

Composer 不止可以自动加载命名空间,还可以加载全局函数。怎么实现的呢?把全局函数写到特定的文件里面去,在程序运行前挨个 require就行了。这个就是 composer 自动加载的第五步,加载全局函数。

if ($useStaticLoader) {    $includeFiles = Composer\Autoload\ComposerStaticInit7b790917ce8899df9af8ed53631a1c29::$files;} else {    $includeFiles = require __DIR__ . '/autoload_files.php';}foreach ($includeFiles as $fileIdentifier => $file) {    composerRequire7b790917ce8899df9af8ed53631a1c29($fileIdentifier, $file);}

跟核心类的初始化一样,全局函数自动加载也分为两种:静态初始化和普通初始化,静态加载只支持PHP5.6以上并且不支持HHVM。

静态初始化:

ComposerStaticInit7b790917ce8899df9af8ed53631a1c29::$files:

public static $files = array ('0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php','667aeda72477189d0494fecd327c3641' => __DIR__ . '/..' . '/symfony/var-dumper/Resources/functions/dump.php',...);

普通初始化

autoload_files:

$vendorDir = dirname(dirname(__FILE__));$baseDir = dirname($vendorDir);    return array('0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php','667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php',   ....);

其实跟静态初始化区别不大。

加载全局函数

class ComposerAutoloaderInit7b790917ce8899df9af8ed53631a1c29{  public static function getLoader(){      ...      foreach ($includeFiles as $fileIdentifier => $file) {        composerRequire7b790917ce8899df9af8ed53631a1c29($fileIdentifier, $file);      }      ...  }}function composerRequire7b790917ce8899df9af8ed53631a1c29($fileIdentifier, $file) {    if (empty(\$GLOBALS['__composer_autoload_files'][\$fileIdentifier])) {        require $file;        $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;    }}

第五部分 —— 运行

到这里,终于来到了核心的核心—— composer 自动加载的真相,命名空间如何通过 composer 转为对应目录文件的奥秘就在这一章。
前面说过,ClassLoader 的 register() 函数将 loadClass() 函数注册到 PHP 的 SPL 函数堆栈中,每当 PHP 遇到不认识的命名空间时就会调用函数堆栈的每个函数,直到加载命名空间成功。所以 loadClass() 函数就是自动加载的关键了。

看下 loadClass() 函数:

public function loadClass($class){    if ($file = $this->findFile($class)) {        includeFile($file);        return true;    }}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);    }    // class map lookup    if (isset($this->classMap[$class])) {        return $this->classMap[$class];    }    if ($this->classMapAuthoritative) {        return false;    }    $file = $this->findFileWithExtension($class, '.php');    // Search for Hack files if we are running on HHVM    if ($file === null && defined('HHVM_VERSION')) {        $file = $this->findFileWithExtension($class, '.hh');    }    if ($file === null) {        // Remember that this class does not exist.        return $this->classMap[$class] = false;    }    return $file;}

我们看到 loadClass() ,主要调用 findFile() 函数。findFile() 在解析命名空间的时候主要分为两部分:classMap 和 findFileWithExtension() 函数。classMap 很简单,直接看命名空间是否在映射数组中即可。麻烦的是 findFileWithExtension() 函数,这个函数包含了 PSR0 和 PSR4 标准的实现。还有个值得我们注意的是查找路径成功后 includeFile() 仍然是外面的函数,并不是 ClassLoader 的成员函数,原理跟上面一样,防止有用户写 $this 或 self。还有就是如果命名空间是以\开头的,要去掉\然后再匹配。

看下 findFileWithExtension 函数:

private function findFileWithExtension($class, $ext){    // PSR-4 lookup    $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;        $first = $class[0];    if (isset($this->prefixLengthsPsr4[$first])) {        foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) {            if (0 === strpos($class, $prefix)) {                foreach ($this->prefixDirsPsr4[$prefix] as $dir) {                    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 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;                    }                }            }        }    }        // 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;    }}

最后小结

我们通过举例来说下上面代码的流程:

如果我们在代码中写下 new phpDocumentor\Reflection\Element(),PHP 会通过 SPL_autoload_register 调用 loadClass -> findFile -> findFileWithExtension。步骤如下:

  • 将 \ 转为文件分隔符/,加上后缀php,变成 $logicalPathPsr4, 即 phpDocumentor/Reflection//Element.php;

  • 利用命名空间第一个字母p作为前缀索引搜索 prefixLengthsPsr4 数组,查到下面这个数组:

        p' =>             array (                'phpDocumentor\\Reflection\\' => 25,                'phpDocumentor\\Fake\\' => 19,          )
  • 遍历这个数组,得到两个顶层命名空间 phpDocumentor\Reflection\ 和 phpDocumentor\Fake\

  • 在这个数组中查找 phpDocumentor\Reflection\Element,找出 phpDocumentor\Reflection\ 这个顶层命名空间并且长度为25。

  • 在prefixDirsPsr4 映射数组中得到phpDocumentor\Reflection\ 的目录映射为:

    'phpDocumentor\\Reflection\\' =>         array (            0 => __DIR__ . '/..' . '/phpdocumentor/reflection-common/src',            1 => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src',            2 => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src',        ),
  • 遍历这个映射数组,得到三个目录映射;

  • 查看 “目录+文件分隔符//+substr(&dollar;logicalPathPsr4, &dollar;length)”文件是否存在,存在即返回。这里就是
    '__DIR__/../phpdocumentor/reflection-common/src + substr(phpDocumentor/Reflection/Element.php,25)'

  • 如果失败,则利用 fallbackDirsPsr4 数组里面的目录继续判断是否存在文件

感谢你能够认真阅读完这篇文章,希望小编分享的“composer中自动加载原理的示例分析”这篇文章对大家有帮助,同时也希望大家多多支持编程网,关注编程网行业资讯频道,更多相关知识等着你来学习!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯