高校图书馆网站建设,能被百度收录的建站网站,公司做网站费用怎么记账,世界十大建筑设计事务所类的自动加载是框架中非常重要的特性#xff0c;它允许你在使用类时无需手动包含或引入对应的文件。类的自动加载实现起来很简单#xff0c;只需这样的一个函数spl_autoload_register就能实现。但框架都有各自的加载规范#xff0c;并不是所有类都能被自动加载#xff0c;因…类的自动加载是框架中非常重要的特性它允许你在使用类时无需手动包含或引入对应的文件。类的自动加载实现起来很简单只需这样的一个函数spl_autoload_register就能实现。但框架都有各自的加载规范并不是所有类都能被自动加载因此这节内容大家还可以了解到PSR-4的自动加载规范另外也可以弄明白通过composer引入进来的类是如何被加载的。
带着我们的好奇心开始我们thinkphp源码之旅打开入口文件public/index.php
require __DIR__ . /../vendor/autoload.php;// 执行HTTP应用并响应
$http (new App())-http;
$response $http-run();
$response-send();
$http-end($response);第一行代码就是载入composer的自动加载文件autoload.php实现类的自动加载。
我们接下来重点研究一下autoload.php在vender目录下可以找到该文件
if (PHP_VERSION_ID 50600) {/**版本相关的限制省略代码**/
}
// 引入autoload_real.php这个类是由composer自动生产的
require_once __DIR__ . /composer/autoload_real.php;// 调用该类里面的getLoader方法这个类名有点长这也是composer自动生成的
return ComposerAutoloaderInit71a72e019c6be19dac67146f3f5fb8de::getLoader();
接下来看看getLoader()方法做了什么
public static function getLoader()
{ // 如果$loader不为空说明已经经过一些列的初始化了就直接返回了if (null ! self::$loader) {return self::$loader;}// php版本相关的检查这里就不细讲require __DIR__ . /platform_check.php;// spl_autoload_register这个函数很重要后面类的自动加载就是用这个函数这里先给大家预热一波这个函 // 数的用法具体的大家可以看看文档https://www.php.net/manual/zh/function.spl-autoload- register// 这行代码的意思就是把当前类里的loadClassLoader函数作为__autoload 的实现/*public static function loadClassLoader($class){if (Composer\Autoload\ClassLoader $class) {// 其实就是引入当ClassLoader类require __DIR__ . /ClassLoader.php;}}*/spl_autoload_register(array(ComposerAutoloaderInit71a72e019c6be19dac67146f3f5fb8de, loadClassLoader), true, true);// new ClassLoader的时候会自动执行前面装载的函数loadClassLoader引入ClassLoader.php// 这样就实现了类的自动加载(引入)// 问题1为什么这里不使用require直接引入这样不是更简单一些吗self::$loader $loader new \Composer\Autoload\ClassLoader(\dirname(__DIR__)); // 这里是删除loadClassLoader释放资源spl_autoload_unregister(array(ComposerAutoloaderInit71a72e019c6be19dac67146f3f5fb8de, loadClassLoader));// 这个文件定义了一些变量里面是Psr4的相关协议后面类的自动加载的时候会使用到等下会重点讲这个东西require __DIR__ . /autoload_static.php;// 这段代码是执行一个回调函数等下会让你看明白call_user_func(\Composer\Autoload\ComposerStaticInit71a72e019c6be19dac67146f3f5fb8de::getInitializer($loader));$loader-register(true);$filesToLoad \Composer\Autoload\ComposerStaticInit71a72e019c6be19dac67146f3f5fb8de::$files;$requireFile \Closure::bind(static function ($fileIdentifier, $file) {if (empty($GLOBALS[__composer_autoload_files][$fileIdentifier])) {$GLOBALS[__composer_autoload_files][$fileIdentifier] true;require $file;}}, null, null);foreach ($filesToLoad as $fileIdentifier $file) {$requireFile($fileIdentifier, $file);}return $loader;
}现在我们看看autoload_static.php文件内容
class ComposerStaticInit71a72e019c6be19dac67146f3f5fb8de
{ // 使用composer加载进来的类你可以使用composer require topthink/think-captcha// 引入验证码类你会发现这里会多了一项内容试试看public static $files array (9b552a3cc426e3287cc811caefa3cf53 __DIR__ . /.. . /topthink/think-helper/src/helper.php,35fab96057f1bf5e7aba31a8a6d5fdde __DIR__ . /.. . /topthink/think-orm/stubs/load_stubs.php,0e6d7bf4a5811bfa5cf40c5ccd6fae6a __DIR__ . /.. . /symfony/polyfill-mbstring/bootstrap.php,667aeda72477189d0494fecd327c3641 __DIR__ . /.. . /symfony/var-dumper/Resources/functions/dump.php,);// 一时半会不知道怎么描述这个东西只好直白一点了// $prefixLengthsPsr4是个二维数组think\\trace\\这是命名空间作为键名然后长度作为值注意这里// “\\”只能算一个字符因为反斜杠是转义符最外层是使用命名空间的第一个字符作为键名public static $prefixLengthsPsr4 array (t array (think\\trace\\ 12,think\\ 6,),// 省略部分代码);// 这个变量定义的是命名空间对应的目录就是对目录进行归类后面自动加载类的时候只有满足了这些对应关系的 // 类才能被加载后面你将深有体会public static $prefixDirsPsr4 array (think\\trace\\ array (0 __DIR__ . /.. . /topthink/think-trace/src,),think\\ array (0 __DIR__ . /.. . /topthink/framework/src/think,1 __DIR__ . /.. . /topthink/think-filesystem/src,2 __DIR__ . /.. . /topthink/think-helper/src,3 __DIR__ . /.. . /topthink/think-orm/src,),// 省略部分代码);// extend是不是很熟悉自定义的类就是放在这个目录public static $fallbackDirsPsr0 array (0 __DIR__ . /../.. . /extend,);// 这个可以理解为缓存变量后面也会用到public static $classMap array (Composer\\InstalledVersions __DIR__ . /.. . /composer/InstalledVersions.php,);// 这是一个初始化函数实现对象之间的变量复制简单的说就是把一个类里面的成员变量的值赋给另一个类public static function getInitializer(ClassLoader $loader){ // 这里返回的是一个Closure对象Closure::bind后面很多地方都用到这个函数// 大家可以看官方文档https://www.php.net/manual/zh/closure.bindreturn \Closure::bind(function () use ($loader) {// 这里的$loader其实就是ClassLoader类这个函数的功能就是将当前类的这些成员变量的值赋值给// ClassLoader.php这个类里面的成员变量$loader-prefixLengthsPsr4 ComposerStaticInit71a72e019c6be19dac67146f3f5fb8de::$prefixLengthsPsr4;$loader-prefixDirsPsr4 ComposerStaticInit71a72e019c6be19dac67146f3f5fb8de::$prefixDirsPsr4;$loader-fallbackDirsPsr0 ComposerStaticInit71a72e019c6be19dac67146f3f5fb8de::$fallbackDirsPsr0;$loader-classMap ComposerStaticInit71a72e019c6be19dac67146f3f5fb8de::$classMap;}, null, ClassLoader::class);}
}我们再回来看看这行代码
// call_user_func函数的作用就是把第一个参数作为回调函数调用也就是说把Closure对象作为一个函数调用实现
// 对象与对象之间的变量复制call_user_func具体用法可以看文档
// https://www.php.net/manual/zh/function.call-user-func
call_user_func(\Composer\Autoload\ComposerStaticInit71a72e019c6be19dac67146f3f5fb8de::getInitializer($loader));继续阅读源码
// 这个函数的核心代码
$loader-register(true);// 下面的代码是引入composer加载进来的类获取autoload_static.php里面的$files
$filesToLoad \Composer\Autoload\ComposerStaticInit71a72e019c6be19dac67146f3f5fb8de::$files;// 这里使用了内置函数Closure::bind定义了一个匿名函数这个函数的作用其实就是引入相关类
$requireFile \Closure::bind(static function ($fileIdentifier, $file) {// 判断全局变量是否有该类已经被引入的标识if (empty($GLOBALS[__composer_autoload_files][$fileIdentifier])) {// 存储一个标识下次就不用重复引入$GLOBALS[__composer_autoload_files][$fileIdentifier] true;require $file;}
}, null, null);
// 这里就是一个循环调用引入相关类
foreach ($filesToLoad as $fileIdentifier $file) {$requireFile($fileIdentifier, $file);
}
return $loader;这部分代码中我们又接触到了Closure::bind它绑定了一个静态的匿名函数这函数里面的内容是这样的先判断类是否被引入过如果没有则使用require引入并且在全局变量中存储一个加载的标识。
简单的讲你可以把它看成一个函数它就像你平时写的函数一样
function requireFile($fileIdentifier, $file){……………………………………
} 其实很多人会有这样的一个疑问为什么要使用Closure::bind而不是直接在foreach里面写逻辑另外写一个函数也行答案就留给大家思考。
接下来就重点看看最核心的一个函数register(true)
public function register($prepend false)
{ // 类的自动加载注册函数一切逻辑都在loadClass这个函数里面spl_autoload_register(array($this, loadClass), true, $prepend);if (null $this-vendorDir) {return;}if ($prepend) {self::$registeredLoaders array($this-vendorDir $this) self::$registeredLoaders;} else {unset(self::$registeredLoaders[$this-vendorDir]);self::$registeredLoaders[$this-vendorDir] $this;}
}重点就在第一行代码spl_autoload_register装载了loadClass这样一个函数
public function loadClass($class)
{ // 判断“被引入的类”文件是否存在if ($file $this-findFile($class)) {// self::$includeFile当前类的成员变量它是一个Closure对象在初始化当前类里面就已经被定义了/*public function __construct($vendorDir null){$this-vendorDir $vendorDir;// 这里定义了这个Closure对象self::initializeIncludeClosure();}*/$includeFile self::$includeFile;$includeFile($file);return true;}return null;
}接下来我们看看$this-findFile($class)
public function findFile($class)
{// 判断当前类的成员变量classMap是否存储了“被引入类”的路径这个变量的初始化内容其实就 // 是 autoload_static.php的$classMap if (isset($this-classMap[$class])) {return $this-classMap[$class];}// 判断“被引入类”是否存在不存在直接返回falseif ($this-classMapAuthoritative || isset($this-missingClasses[$class])) {return false;}// 这段代码其实就是从缓存中获取类的路径目的就是提高框架的初始化速度因为框架每次运行都要引入几十个类。if (null ! $this-apcuPrefix) {// 获取缓存内容apcu_fetch函数大家可以看官方文档// https://www.php.net/manual/zh/function.apcu-fetch$file apcu_fetch($this-apcuPrefix.$class, $hit);if ($hit) {return $file;}}// 这个函数的核心代码$file $this-findFileWithExtension($class, .php);// 这段代码是跟黑客相关的防止黑客入侵一些hh类型文件if (false $file defined(HHVM_VERSION)) {$file $this-findFileWithExtension($class, .hh);}// 这里就是把加载类路径缓存起来if (null ! $this-apcuPrefix) {// apcu_add跟apcu_fetch一样去看看官方文档apcu_add($this-apcuPrefix.$class, $file);}if (false $file) {// 如果这个文件不存在就存一个标识下次就直接返回false即可$this-missingClasses[$class] true;}return $file;
}下面我们来看看这个函数中最核心的一行代码
$file $this-findFileWithExtension($class, .php);进入findFileWithExtension
// 我们以一个例子来讲new think\Exception()这是框架载入的第一个类此时传进来
// 的$class是think\Exception
private function findFileWithExtension($class, $ext)
{ // $logicalPathPsr4 think\Exception.php$logicalPathPsr4 strtr($class, \\, DIRECTORY_SEPARATOR) . $ext;// 获取第一个字符t为什么$first $class[0];// 判断prefixLengthsPsr4这个数组中是否存在“t”这个元素这里的prefixLengthsPsr4就是我们前面提到 // psr4协议规范的内容你可以打开autoload_static.php看看很显然是存在的/*t array (think\\trace\\ 12,think\\captcha\\ 14,think\\ 6,),*/if (isset($this-prefixLengthsPsr4[$first])) {$subPath $class;while (false ! $lastPos strrpos($subPath, \\)) {$subPath substr($subPath, 0, $lastPos);// 这里的目的就是得到think\\这样的一个命名空间$search $subPath . \\;// 那接下来就是找该命名空间下面的目录/*think\\ array (0 __DIR__ . /.. . /topthink/framework/src/think,1 __DIR__ . /.. . /topthink/think-filesystem/src,2 __DIR__ . /.. . /topthink/think-helper/src,3 __DIR__ . /.. . /topthink/think-orm/src,),*/if (isset($this-prefixDirsPsr4[$search])) {$pathEnd DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos 1);foreach ($this-prefixDirsPsr4[$search] as $dir) {// 遍历这四个目录看看是否可以找到think\Exception.phpif (file_exists($file $dir . $pathEnd)) {// 最后返回F:\phpstudy_pro\WWW\thinkphp8\vendor// \composer/../topthink/framework/src/think\Exception.phpreturn $file;}}}}}// PSR-4 fallback dirsforeach ($this-fallbackDirsPsr4 as $dir) {if (file_exists($file $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {return $file;}}// 后面这部分代码是涉及到PSR-0这里就不讲了框架好像也并没有使用这种协议但好像有个比较特别的地方// PSR-0 fallback dirs// 我们在autoload_static.php中看到$fallbackDirsPsr0这样一个变量而不是$fallbackDirsPsr4// 这样很让人费解我也不知道是什么原因// 这段代码其实就是定义了类的扩展目录也就是说你自己的类放在extend这个目录里面会被框架自动加载foreach ($this-fallbackDirsPsr0 as $dir) {if (file_exists($file $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {return $file;}}return false;
}相信看到这里大家对类的自动加载有了一定的认识。
记得我刚才出来那会犯过这样的一个错误就是把一个项目中通过composer引入的类复制到另一个项目发现运行不了阅读源码之后才发现了真实的原因.