很多框架在初始化的时候都会引入 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实现自动加载需要用到的源文件。

  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('ComposerAutoloaderInit2c981d0f72838b8ba6448f6427ca961d', 'loadClassLoader'), true, true);
//获取自动PSR4自动加载的规则
     self::$loader = $loader = new \Composer\Autoload\ClassLoader();
​
     spl_autoload_unregister(
array('ComposerAutoloaderInit2c981d0f72838b8ba6448f6427ca961d', '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;
  }

第一部分——单例

经典的单例模式,保证自动加载类只能有一个。

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

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

第二部分 new 一个自动加载的核心类对象。当类不存在就直接通过spl_autoload_register注册到当前对象。注册函数体为loadClassLoader

<?php
 /***********************获得自动加载核心类对象********************/
 spl_autoload_register(  array('ComposerAutoloaderInit7b790917ce8899df9af8ed53631a1c29', 'loadClassLoader'), true, true);
​
 self::$loader = $loader = new \Composer\Autoload\ClassLoader();
//注册成功并获取到对象后,直接进行销毁
 spl_autoload_unregister(
array('ComposerAutoloaderInit7b790917ce8899df9af8ed53631a1c29', 'loadClassLoader'));

loadClassLoader()函数:

<?php
//自动注册回调方法
public 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 {
     //导入composer定义的命名空间规则
     $map = require __DIR__ . '/autoload_namespaces.php';
     foreach ($map as $namespace => $path) {
        $loader->set($namespace, $path);
    }
//引入psr4的加载规范,并设置到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);
    }
  }

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

初始化的方法有两种,根据系统环境来进行注册方式的选择:

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

3.1autoload_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 类 里的成员变量。

关于匿名函数的绑定功能

3.2 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\\Controller' => __DIR__ . '/../..' . '/app/Http/Controllers/Controller.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',
...)

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

3.3 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,先验证磁盘上这个文件是否存在,如果不存在接着遍历。如果遍历后没有找到,则加载失败。

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

3.5 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 。

3.6 命名空间映射

autoload_classmap:

<?php
public 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() 函数,然后找到命名空间对应的文件。

4.全局函数的自动加载

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。

4.1 静态初始化:

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

4.2 普通初始化

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',
  ....
);

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

4.3 加载全局函数

class composerRequire2c981d0f72838b8ba6448f6427ca961d{
 public static function getLoader(){
    ...
     foreach ($includeFiles as $fileIdentifier => $file) {      composerRequire7b790917ce8899df9af8ed53631a1c29($fileIdentifier, $file);
    }
    ...
}
}
//根据获取到全局函数的数组来引入相关文件
function composerRequire2c981d0f72838b8ba6448f6427ca961d($fileIdentifier, $file)
{
   if (empty(\$GLOBALS['__composer_autoload_files'][\$fileIdentifier])) {
       require $file;
​
       $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
  }
}

第五部分 —— 运行

前面说过,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 Sixstar\Winner\Element(),PHP 会通过 SPL_autoload_register 调用 loadClass -> findFile -> findFileWithExtension。步骤如下:

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

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

      p' =>
          array (
              'Sixstar\\Winner\\' => 15,
              'Sixstar\\Fake\\' => 13,
        )

  • 遍历这个数组,得到两个顶层命名空间 Sixstar\Winner\ 和 Sixstar\Fake\

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

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

'Sixstar\\Winner\\' => array ( 0 => __DIR__ . '/..' . '/Sixstar/Winner-common/src', 1 => __DIR__ . '/..' . '/Sixstar/type-resolver/src', 2 => __DIR__ . '/..' . '/Sixstar/Winner-docblock/src', ),

  • 遍历这个映射数组,得到三个目录映射;

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

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


如果自己在使用第三方包的过程中,自己定义的根命名空间和第三方包的命名空间一样,则会以数组的形式存储,如

'Fairy\' => array($baseDir . '/app', $vendorDir . '/cshaptx4869/work/src/Fairy'),

tr