composer--------psr4加载流程解析
前言
平时工作中,一直在使用composer解决一些包依赖管理,自动加载等业务场景,但是一直没有好好看过vendor/composer目录下面的文件,今天看了下源码,也算清楚了内部的文件执行流程。
主要文件:
vendor/autoload.php 入口文件
vendor/composer/autoload_real.php 真正加载文件
vendor/composer/ClassLoader.php 内部加载器文件
vendor/composer/autoload_static.php 当php版本大于等于5.6时,内部加载器会用此文件里面的配置映射信息填充相关数组
vendor/composer/autoload_classmap.php 当php版本小于5.6时,内部加载器会用此文件里面的配置映射信息填充加载器的classMap数组
vendor/composer/autoload_files.php 当php版本小于5.6时,真正加载文件会在内部直接require进autoload_files文件里面的所有文件
vendor/composer/autoload_psr4.php 当php版本小于5.6时,包含符合psr4标准的所有 命名空间:对应查找路径 的映射信息
psr4标准自动加载案例分析:
1、入口文件,我使用了Think\wahaha\Chu这个测试类
<?php require './composer/vendor/autoload.php'; use Think\wahaha\Chu; echo Chu::getName();
2、composer.json中的psr4配置信息
"autoload": { "psr-4": { "Think\\": "think/" } }
3、目录结构
think
wahaha
Chu.php
vendor
autoload.php
composer
ClassLoader.php
.......
4、自动加载类使用时,内部文件执行过程分析
- 入口文件加载autoload.php,而autoload.php该文件引入autoload_real.php,执行了autoload_real.php文件中类的getLoader()静态方法
private static $loader; //引入类加载器文 public static function loadClassLoader($class) { if ('Composer\Autoload\ClassLoader' === $class) { require __DIR__ . '/ClassLoader.php'; } } public static function getLoader() { //判断类加载器实例是否存在 if (null !== self::$loader) { return self::$loader; } //注册类本身的loadClassLoader方法为自动加载方法 spl_autoload_register(array('ComposerAutoloaderInitf4cf8dfb98c23a8977f6da4c2c099d38', 'loadClassLoader'), true, true); //实例化类加载器 self::$loader = $loader = new \Composer\Autoload\ClassLoader(); spl_autoload_unregister(array('ComposerAutoloaderInitf4cf8dfb98c23a8977f6da4c2c099d38', 'loadClassLoader')); //判断运行环境信息 $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); if ($useStaticLoader) {//php大于5.6时 /* 加载classmap,files,psr4相关映射配置文件 类加载器相关数组配置信息初始化 prefixLengthsPsr4、classMa、prefixDirsPsr4 */ require_once __DIR__ . '/autoload_static.php';
//调用返回的回调函数对象,其中回调函数中进行类加载器实例的一些相关数组配置信息初始化 call_user_func(\Composer\Autoload\ComposerStaticInitf4cf8dfb98c23a8977f6da4c2c099d38::getInitializer($loader)); } else {//php小于5.6时 //加载命名空间与查找路径映射配置信息文件,并将类加载器相关数组配置初始化 $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映射配置信息文件,并将类加载器相关数组配置初始化 $classMap = require __DIR__ . '/autoload_classmap.php'; if ($classMap) { $loader->addClassMap($classMap); } } //注册类加载器实例的loadClass方法为自动加载方法,实现类的自动加载 $loader->register(true); //获取files映射信息 if ($useStaticLoader) { $includeFiles = Composer\Autoload\ComposerStaticInitf4cf8dfb98c23a8977f6da4c2c099d38::$files; } else { $includeFiles = require __DIR__ . '/autoload_files.php'; } //加载files数组中的相关文件 foreach ($includeFiles as $fileIdentifier => $file) { composerRequiref4cf8dfb98c23a8977f6da4c2c099d38($fileIdentifier, $file); } return $loader; }
- 看看类加载器ClassLoader.php文件中的loadclass方法里做了什么
public function loadClass($class) { //查找类名对应的实际文件地址,并引入该文件 if ($file = $this->findFile($class)) { includeFile($file); return true; } } public function findFile($class) { // 查找类是否存在classMap数组中,如果在就返回类的文件路径 if (isset($this->classMap[$class])) { return $this->classMap[$class]; } if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { return false; } if (null !== $this->apcuPrefix) { $file = apcu_fetch($this->apcuPrefix.$class, $hit); if ($hit) { return $file; } } //psr4标准查找 $file = $this->findFileWithExtension($class, '.php'); .............. } private function findFileWithExtension($class, $ext) { // PSR-4 lookup $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;//将类名拼接上文件扩展名 Think\wahaha\Chu.php $first = $class[0];//获取类名的首字母 T if (isset($this->prefixLengthsPsr4[$first])) {//通过首字符查找对应的项 例如:[T=>['Think\\'=>6]],详情可查看autoload_static.php文件 $subPath = $class; while (false !== $lastPos = strrpos($subPath, '\\')) {//将类名拆分,例如 Think\wahaha\Chu => Think\wahaha、Think $subPath = substr($subPath, 0, $lastPos); $search = $subPath.'\\'; // Think\ Think\wahaha\ if (isset($this->prefixDirsPsr4[$search])) { //通过search项查找对应的目录数组 例如: ['Think\\'=>[ 0 => __DIR__ . '/../..' . '/think']],,详情可查看autoload_static.php文件 foreach ($this->prefixDirsPsr4[$search] as $dir) {//循环遍历目录数组 $length = $this->prefixLengthsPsr4[$first][$search];//获取命名空间字符长度 例如 Think\ 长度为6
/*
拼接路径:
__DIR__ . '/../..' . '/think' / wahaha\Chu.php
*/ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { return $file; } } } } } .......................... }
结语:
至此,psr4分析差不多就完了,更多细节可以自己参考里面的代码,源码中使用了匿名函数类(autoload_static.php文件中),导致刚开始有点懵,由于自己平时这块使用的比较少,所以需要再这块进行加强,平时看了一些事件机制实现,大部分都是观察者模式进行绑定和通知的。