TP5类的自动加载机制核心主要由Loader类完成,所以我们主要围绕该类来分析。
1、Loader类的属性列表
/**
* 类名映射信息
* @var array
*/
protected static $classMap = [];
/**
* 类库别名
* @var array
*/
protected static $classAlias = [];
/**
* PSR-4
* @var array
*/
private static $prefixLengthsPsr4 = [];
private static $prefixDirsPsr4 = [];
private static $fallbackDirsPsr4 = [];
/**
* PSR-0
* @var array
*/
private static $prefixesPsr0 = [];
private static $fallbackDirsPsr0 = [];
/**
* 需要加载的文件
* @var array
*/
private static $files = [];
/**
* Composer安装路径
* @var string
*/
private static $composerPath;
重要属性
$classMap:类库映射关系命名空间对应相应类文件路径
$prefixLengthsPsr4:类库命名空间对应命名空间字符长度
$prefixDirsPsr4:类库命名空间对应的类库文件所在的文件目录位置
$fallbackDirsPsr4:自动加载类库目录
2、注册自动加载机制流程
自动加载机制流程主要由Loader类的静态方法register来实现的,方法代码如下
2.1 注册系统自动加载
spl_autoload_register($autoload ?: 'think\\Loader::autoload', true, true);
实例化找不到的类时将调用 think\Loader::autoload 方法
2.1.2 Composer自动加载支持
引入composer目录(\vendor\composer\)下的autoload_static.php文件
public static $prefixLengthsPsr4 = array ( // 类库文件的命名空间以及字符长度
't' =>
array (
'think\\composer\\' => 15,
),
'a' =>
array (
'app\\' => 4,
),
);
public static $prefixDirsPsr4 = array ( // 命名空间对应的类库文件所在的文件目录位置
'think\\composer\\' =>
array (
0 => __DIR__ . '/..' . '/topthink/think-installer/src',
),
'app\\' =>
array (
0 => __DIR__ . '/../..' . '/application',
),
);
public static $classMap = array (
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
);
当我们通过composer方式下载插件到项目的时候,composer命令会修改autoload_static.php中的prefixLengthsPsr4,prefixDirsPsr4这两个属性,指定该扩展的命名空间和对应目录。所以如果我们直接复制扩展到verdon目录的时候,需要修改这两个属性值,不然框架加载不到这个扩展。但是建议用composer命令来下载扩展,让命令自动帮我们修改。
2.1.3 将属性prefixLengthsPsr4、prefixDirsPsr4、classMap属性赋值给Loader::$property对应属性
// 项目的根目录 $rootPath = D:\phpstudy_pro\WWW\thinkphpStudy\
$rootPath = self::getRootPath();
// Composer目录 self::$composerPath = D:\...\thinkphpStudy\vendor\composer\
self::$composerPath = $rootPath . 'vendor' . DIRECTORY_SEPARATOR . 'composer' . DIRECTORY_SEPARATOR;
// Composer自动加载支持
if (is_dir(self::$composerPath)) {
if (is_file(self::$composerPath . 'autoload_static.php')) {
require self::$composerPath . 'autoload_static.php';
// 返回由当前运行脚本中已定义类的名字组成的数组
// $declaredClass = array(151) {
// [0] => string(9) "Exception" ...
// [149] => string(12) "think\Loader"
// [150] => string(68) "Composer\Autoload\ComposerStaticInitaa210877af5760b863ea566b06fb95ca"
// }
$declaredClass = get_declared_classes();
// $composerClass = Composer\Autoload\ComposerStaticInitaa210877af5760b863ea566b06fb95ca
$composerClass = array_pop($declaredClass);
foreach (['prefixLengthsPsr4', 'prefixDirsPsr4', 'fallbackDirsPsr4', 'prefixesPsr0', 'fallbackDirsPsr0', 'classMap', 'files'] as $attr) {
// property_exists函数判断Composer的autoload_static类是否包含以上属性
// 包含则把属性赋值给Loader类
if (property_exists($composerClass, $attr)) {
self::${$attr} = $composerClass::${$attr};
}
}
} else {
self::registerComposerLoader(self::$composerPath);
}
}
相当于Loader::prefixLengthsPsr4, Loader::prefixDirsPsr4包含了Composer autoload_static 类下面的命名空间和对应的类库目录路径
2.1.4 命名空间think,traits加载支持
self::addNamespace([
'think' => __DIR__,
'traits' => dirname(__DIR__) . DIRECTORY_SEPARATOR . 'traits'
]);
在Loader类的prefixLengthsPsr4、prefixDirsPsr4属性中添加think、traits命名空间及对应的类库文件目录路径
2.1.5 加载类库映射文件
if (is_file($rootPath . 'runtime' . DIRECTORY_SEPARATOR . 'classmap.php')) {
self::addClassMap(__include_file($rootPath . 'runtime' . DIRECTORY_SEPARATOR . 'classmap.php'));
}
可使用 php think optimize:autoload命令生成类库映射文件 提高系统自动加载的性能 (每次有添加文件或者修改文件路径或名称需要重新生成该文件)
2.1.6 自动加载类库目录
self::addAutoLoadDir($rootPath . 'extend');
Loader::addAutoLoadDir()方法将extend目录加入$fallbackDirsPsr4中,当实例化找不到的类会在自动加载类库目录中查找是否存在该类
2.2 自动加载函数Loader::autoload()
2.2.1 autoload方法代码
public static function autoload($class)
{
// 类是否设置别名
if (isset(self::$classAlias[$class])) {
return class_alias(self::$classAlias[$class], $class);
}
if ($file = self::findFile($class)) {
// Win环境严格区分大小写
if (strpos(PHP_OS, 'WIN') !== false && pathinfo($file, PATHINFO_FILENAME) != pathinfo(realpath($file), PATHINFO_FILENAME)) {
return false;
}
__include_file($file);
return true;
}
}
首先在Loader::$classAlias类别名属性中是否存在该类,如果存在则使用class_alias重新new这个类,正常情况将调用findFile方法查找类库文件路径include引入文件
2.2.2 findFile过程
Loader::findFile()主要根据prefixLengthsPsr4、prefixDirsPsr4、fallbackDirsPsr4、classMap属性来确定加载类库的文件路径,明白这些属性的作用就能够理解TP框架类的自动加载核心思想
/ Loader::$classMap 打印结果
array(1) {
["Composer\InstalledVersions"]=>
string(83) "F:\phpstudy_pro\WWW\thinkphpStudy\vendor\composer/../composer/InstalledVersions.php"
}
/ Loader::$prefixLengthsPsr4 打印结果
array(2) {
["t"]=>
array(3) {
["think\composer\"]=>
int(15)
["think\"]=>
int(6)
["traits\"]=>
int(7)
}
["a"]=>
array(1) {
["app\"]=>
int(4)
}
}
/ Loader::$prefixDirsPsr4 打印结果
array(4) {
["think\composer\"]=>
array(1) {
[0]=>
string(81) "F:\phpstudy_pro\WWW\thinkphpStudy\vendor\composer/../topthink/think-installer/src"
}
["app\"]=>
array(1) {
[0]=>
string(67) "F:\phpstudy_pro\WWW\thinkphpStudy\vendor\composer/../../application"
}
["think\"]=>
array(1) {
[0]=>
string(56) "F:\phpstudy_pro\WWW\thinkphpStudy\thinkphp\library\think"
}
["traits\"]=>
array(1) {
[0]=>
string(57) "F:\phpstudy_pro\WWW\thinkphpStudy\thinkphp\library\traits"
}
}
/ Loader::$fallbackDirsPsr4 打印结果
array(1) {
[0]=>
string(40) "F:\phpstudy_pro\WWW\thinkphpStudy\extend"
}
private static function findFile($class)
{
//$class = think\Error
if (!empty(self::$classMap[$class])) {
// 类库映射
return self::$classMap[$class];
}
// 查找 PSR-4 $logicalPathPsr4 = think/Error.php
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . '.php';
$first = $class[0]; // t
if (isset(self::$prefixLengthsPsr4[$first])) {
// $prefix $length = think\composer 15 | think 6 | traits 7
foreach (self::$prefixLengthsPsr4[$first] as $prefix => $length) {
if (0 === strpos($class, $prefix)) {
// $dir = .../thinkphp/library/think
foreach (self::$prefixDirsPsr4[$prefix] as $dir) {
// substr($logicalPathPsr4, $length) 获取类文件名称 Error.php
// $file = .../thinkphp/library/think/Error.php
if (is_file($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) {
return $file;
}
}
}
}
}
// 查找 PSR-4 fallback dirs
foreach (self::$fallbackDirsPsr4 as $dir) { // $dir = .../extend/
// $file = .../extend/think/Error.php
if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
return $file;
}
}
}
主要查找方式 (从上往下查找)
1、classMap类库映射属性中是否存在加载类。
2、获取加载类库的命名空间首字母在prefixLengthsPsr4属性中查询对应的类库的命名空间,循环在命名空间映射的类文件路径属性prefixDirsPsr4数组中寻找是否有对应的类有则返回。
3、fallbackDirsPsr4自动加载类库属性循环遍历在映射目录中是否存在加载类文件有则返回。
3、实战练习巩固
在TP项目中新增util文件目录用来存放软件工具代码类库,使得项目中可以直接实例化调用工具类库使用。
util目录下Game.php
<?php
class Game
{
public function show()
{
echo 'util\Game.php Class Method show';
}
}
util\spl目录下User.php
<?php
namespace spl;
class User
{
public function show()
{
echo 'util\spl\User.php Class Method show';
}
}
在app控制器Index下直接调用Game、User类访问方法
<?php
namespace app\index\controller;
use Di\Container;
class Index
{
public function study()
{
$gameClass = new \Game();
dump($gameClass->show());
$userClass = new \spl\User();
dump($userClass->show());
}
}
浏览器访问
3.1 方法一
在Loader::$fallbackDirsPsr4属性中将util文件目录路径加入
// 自动加载extend目录
self::addAutoLoadDir($rootPath . 'extend');
self::addAutoLoadDir($rootPath . 'util');
// 打印fallbackDirsPsr4属性
array(2) {
[0]=> string(40) "D:\phpstudy_pro\WWW\thinkphpStudy\extend"
[1]=> string(38) "D:\phpstudy_pro\WWW\thinkphpStudy\util"
}
再次浏览器访问
声明:使用该方式创建的类文件命名空间namespace 无需定义util,因为类的自动加载机制是遍历$fallbackDirsPsr4属性存放的类库目录查找是否存在加载的类。
3.1 方法二
在Loader类的prefixLengthsPsr4、prefixDirsPsr4属性中将util的命名空间以及对应的文件目录路径加入
// 注册命名空间定义
self::addNamespace([
'think' => __DIR__,
'traits' => dirname(__DIR__) . DIRECTORY_SEPARATOR . 'traits',
'util' => $rootPath . 'util',
]);
// 打印prefixLengthsPsr4属性
array(3) {
["t"]=>
array(3) {
["think\composer\"]=>
int(15)
["think\"]=>
int(6)
["traits\"]=>
int(7)
}
["a"]=>
array(1) {
["app\"]=>
int(4)
}
["u"]=>
array(1) {
["util\"]=>
int(5)
}
}
// 打印prefixDirsPsr4属性
array(5) {
["think\composer\"]=>
array(1) {
[0]=>
string(81) "D:\phpstudy_pro\WWW\thinkphpStudy\vendor\composer/../topthink/think-installer/src"
}
["app\"]=>
array(1) {
[0]=>
string(67) "D:\phpstudy_pro\WWW\thinkphpStudy\vendor\composer/../../application"
}
["think\"]=>
array(1) {
[0]=>
string(56) "D:\phpstudy_pro\WWW\thinkphpStudy\thinkphp\library\think"
}
["traits\"]=>
array(1) {
[0]=>
string(57) "D:\phpstudy_pro\WWW\thinkphpStudy\thinkphp\library\traits"
}
["util\"]=>
array(1) {
[0]=>
string(38) "D:\phpstudy_pro\WWW\thinkphpStudy\util"
}
}
Index.php
public function study()
{
$gameClass = new \util\Game(); // 实例化也需加上 util
dump($gameClass->show());
$userClass = new \util\spl\User();
dump($userClass->show());
}
声明:使用该方式定义Game.php和User.php类的时命名空间namespace需加上util。
本文是自己需要提升一下自己对php框架的知识储备,从而对thinkphp5框架类的类的自动加载机制进行学习分析并分享学习心得顺便巩固所学,如有问题或者更好的意见和建议欢迎指出,共同进步~~~