前言
PHP 自5.3的版本之后,已然重焕新生,命名空间、性状(trait)、闭包、接口、PSR 规范、以及 composer 的显现已然让 PHP 变成为了一门现代化的脚本语言。PHP 的生态系统亦始终在演进,而 composer 的显现更加是彻底的改变了以往构建 PHP 应用的方式,咱们能够按照 PHP 的应用需要混合搭配最合适的 PHP 组件。当然这亦得益于 PSR 规范的提出
PHP 自动加载功能
PHP 自动加载功能的由来
在 PHP 研发过程中,倘若期盼从外边引入一个 Class ,一般会运用 include 和 require 办法,去把定义这个 Class 的文件包括进来。这个在小规模研发的时候,没什么大问题。但在大型的研发项目中,运用这种方式会带来有些隐含的问题:倘若一个 PHP 文件需要运用非常多其它类,那样就需要非常多的 require/include 语句,这般有可能会 导致遗漏 或 包括进不必要的类文件。倘若海量的文件都需要运用其它的类,那样要保准每一个文件都包括正确的类文件肯定是一个噩梦, 况且 require或 incloud 的性能代价很大。
PHP5 为这个问题供给了一个处理方法,这便是 类的自动加载(autoload)机制。autoload机制 能够使得 PHP 程序有可能在运用类时才自动包括类文件,而不是一起始就将所有的类文件include进来,这种机制亦叫作为 Lazy loading (惰性加载)。
自动加载功能的优点 运用类之前无需 include / require运用类的时候才会 include / require 文件,实现了 lazy loading ,避免了 include / require 多余文件无需思虑引入类的实质磁盘位置 ,实现了规律和实体文件的分离
PHP 自动加载函数 __autoload()
从 PHP5 起始,当咱们在运用一个类时,倘若发掘这个类无加载,就会自动运行 __autoload() 函数,这个函数是咱们在程序中自定义的,在这个函数中咱们能够加载需要运用的类。下面是个简单的示例 <?php
function __autoload($classname) {
require_once ($classname . ".class.php");
}
在咱们这个简单的例子中,咱们直接将类名加上扩展名 .class.php 构成为了类文件名,而后运用 require_once 将其加载
从这个例子中,咱们能够看出 __autoload 最少要做三件事情: 按照类名确定类文件名确定类文件所在的磁盘路径将类从磁盘文件中加载到系统中
第三步最简单,只需要运用 include / require 就可。要实现第1步,第二步的功能,必须在研发时约定类名与磁盘文件的映射办法,仅有这般咱们才可按照类名找到它对应的磁盘文件
当有海量的类文件要包括的时候,咱们只要确定相应的规则,而后在 __autoload() 函数中,将类名与实质的磁盘文件对应起来,就能够实现 lazy loading 的效果
PHP autoload函数说明
__autoload() 函数存在的问题
倘若在一个系统的实现中,倘若需要运用非常多其它的类库,这些类库可能是由于区别的研发人员编写的, 其类名与实质的磁盘文件的映射规则不尽相同。此时倘若要实现类库文件的自动加载,就必须 在 __autoload() 函数中将所有的映射规则所有实现,这般的话 __autoload() 函数有可能会非常繁杂,乃至没法实现。最后可能会引起 __autoload() 函数非常臃肿,此时即便能够实现,亦会给将来的守护和系统效率带来很大的消极影响
那样问题出此刻哪里呢?问题出此刻 __autoload() 是全局函数只能定义一次 ,不足灵活,因此所有的类名与文件名对应的规律规则都要在一个函数里面实现,导致这个函数的臃肿。那样怎样来处理这个问题呢?答案便是运用一个 __autoload调用堆栈 ,区别的映射关系写到区别的 __autoload函数 中去,而后统一注册统一管理,这个便是 PHP5 引入的SPL Autoload 。
SPL Autoload
SPL是 Standard PHP Library(标准PHP库)的缩写。它是 PHP5 引入的一个扩展标准库,包含 spl autoload 关联的函数以及各样数据结构和迭代器的接口或类。spl autoload 关联的函数详细可见 php中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() 函数。
PSR4 标准
PSR-4 规范了怎样指定文件路径从而自动加载类定义,同期规范了自动加载文件的位置。
一个完整的类名需拥有以下结构 完整的类名必须要有一个顶级命名空间,被叫作为 "vendor namespace"完整的类名能够有一个或多个子命名空间完整的类名必须有一个最后的类名完整的类名中任意一部分中的下滑线都是无特殊含义的完整的类名能够由任意体积写字母构成所有类名都必须是体积写敏锐的
按照完整的类名载入相应的文件 完整的类名中,去掉最前面的命名空间分隔符,前面连续的一个或多个命名空间和子命名空间,做为「命名空间前缀」,其必须与最少一个「文件基目录」相对应紧接命名空间前缀后的子命名空间 必须 与相应的「文件基目录」相匹配,其中的命名空间分隔符将做为目录分隔符。末尾的类名必须与对应的以 .php 为后缀的文件同名自动加载器(autoloader)的实现必定不可抛出反常、必定不可触发任一级别的错误信息以及不该该有返回值
例子
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 做了那些事情 你有一个项目依赖于若干个库。其中有些库依赖于其他库。你声明你所依赖的东西。Composer 会找出哪个版本的包需要安装,并安装它们(将它们下载到你的项目中)。
例如,你正在创建一个项目,需要做有些单元测试。你决定运用 phpunit 。为了将它添加到你的项目中,你所需要做的就是在 composer.json 文件里描述项目的依赖关系。 {
"require": {
"phpunit/phpunit":"~6.0",
}
}而后在 composer require 之后咱们只要在项目里面直接 use phpunit 的类就可运用。
执行 composer require 时出现了什么 composer 会找到符合 PR4 规范的第三方库的源将其加载到 vendor 目录下初始化顶级域名的映射并写入到指定的文件里
如:PHPUnit\\Framework\\Assert => __DIR__ . /.. . /phpunit/phpunit/src/Framework/Assert.php
写好一个 autoload 函数,并且注册到 spl_autoload_register()里
题外话:此刻非常多框架都已然帮咱们写好了顶级域名映射了,咱们只需要在框架里面新建文件,在新建的文件中写好命名空间,就能够在任何地区 use 咱们的命名空间了
Composer 源码分析
下面咱们经过对源码的分析来瞧瞧 composer 是怎样实现 PSR4标准 的自动加载功能。
非常多框架在初始化的时候都会引入 composer 来帮助自动加载的,以 Laravel 为例,它入口文件 index.php 第1句便是利用 composer 来实现自动加载功能
起步 <?php
define(LARAVEL_START, microtime(true));
require __DIR__ . /../vendor/autoload.php;
去 vendor 目录下的 autoload.php : <?php
// autoload.php @generated by Composer
require_once __DIR__ . /composer/autoload_real.php;
return ComposerAutoloaderInit6ed409f9f3791196a1d5a1f407fb5184::getLoader();
这儿便是 Composer 真正起始的地区了
Composer自动加载文件
Composer自动加载所用到的源文件。
autoload_real.php: 自动加载功能的引导类composer 加载类的初始化(顶级命名空间与文件路径映射初始化)和注册(spl_autoload_register())ClassLoader.php : composer 加载类composer 自动加载功能的核心类autoload_static.php : 顶级命名空间初始化类用于给核心类初始化顶级命名空间autoload_classmap.php : 自动加载的最简单形式有完整的命名空间和文件目录的映射autoload_files.php : 用于加载全局函数的文件存放各个全局函数所在的文件路径名autoload_namespaces.php : 符合 PSR0 标准的自动加载文件存放着顶级命名空间与文件的映射autoload_psr4.php : 符合 PSR4 标准的自动加载文件存放着顶级命名空间与文件的映射
autoload_real 引导类
在 vendor 目录下的 autoload.php 文件中咱们能够看出,程序重点调用了引导类的静态办法 getLoader() ,咱们接着瞧瞧这个函数
自动加载引导类分为 5 个部分。
第1部分——单例
第1部分很简单,便是个最经典的单例模式,自动加载类只能有一个。 <?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)
);
<?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{
$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);
}
}
这一部分便是对自动加载类的初始化,重点是给自动加载核心类初始化顶级命名空间映射。
初始化的办法有两种: 运用 autoload_static 进行静态初始化调用核心类接口初始化
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 = ComposerStaticInit6ed409f9f3791196a1d5a1f407fb5184:prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit6ed409f9f3791196a1d5a1f407fb5184:prefixDirsPsr4;
$loader->prefixesPsr0 = ComposerStaticInit6ed409f9f3791196a1d5a1f407fb5184:prefixesPsr0;
$loader->classMap = ComposerStaticInit6ed409f9f3791196a1d5a1f407fb5184:classMap;
},null, ClassLoader::class);
}
}
这个静态初始化类的核心便是 getInitializer() 函数,它将自己类中的顶级命名空间映射给了 ClassLoader 类。值得重视的是这个函数返回的是一个匿名函数,为何呢?原由便是 ClassLoader类 中的 prefixLengthsPsr4 、prefixDirsPsr4等等变量都是 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 标准顶级命名空间映射用了两个数组,第1个是用命名空间第1个字母做为前缀索引,而后是 顶级命名空间,然则最后并不是文件路径,而是 顶级命名空间的长度。为何呢?
由于 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_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,咱们已然能够找到它对应的类文件所在位置。那样,它是什么时候被触发去找的呢?
此刻咱们起始引导类的第四部分:注册自动加载核心类对象。咱们来瞧瞧核心类的 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;利用命名空间第1个字母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,
),遍历这个映射数组,得到三个目录映射;
以上便是 composer 自动加载的原理解析
|