类的自动加载
首先我们拿到框,肯定要先找到框架的入口文件,Thinkphp5.1的入口文件 tp5_analyze\public\index.php
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
// [ 应用入口文件 ]
namespace think;
// 加载基础文件
require __DIR__ . '/../thinkphp/base.php';
// 支持事先使用静态方法设置Request对象和Config对象
// 执行应用并响应
Container::get('app')->run()->send();
我们可以看到在入口文件中,他会去加载基础文件 thinkphp/base.php
,基础文件所在目录 tp5_analyze\thinkphp\base.php
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think;
// 载入Loader类
require __DIR__ . '/library/think/Loader.php';
// 注册自动加载
Loader::register();
// 注册错误和异常处理机制
Error::register();
// 实现日志接口
if (interface_exists('Psr\Log\LoggerInterface')) {
interface LoggerInterface extends \Psr\Log\LoggerInterface
{}
} else {
interface LoggerInterface
{}
}
// 注册类库别名
Loader::addClassAlias([
'App' => facade\App::class,
'Build' => facade\Build::class,
'Cache' => facade\Cache::class,
'Config' => facade\Config::class,
'Cookie' => facade\Cookie::class,
'Db' => Db::class,
'Debug' => facade\Debug::class,
'Env' => facade\Env::class,
'Facade' => Facade::class,
'Hook' => facade\Hook::class,
'Lang' => facade\Lang::class,
'Log' => facade\Log::class,
'Request' => facade\Request::class,
'Response' => facade\Response::class,
'Route' => facade\Route::class,
'Session' => facade\Session::class,
'Url' => facade\Url::class,
'Validate' => facade\Validate::class,
'View' => facade\View::class,
]);
在base.php
中会去加载Loader.php
这个文件或者说是类,这个类可以这么说,就是Thinkphp5自动加载的灵魂。可以看到加载的类 Loader.php
,类所在目录thinkphp\library\think\Loader.php
,这个类库就是Thinkphp5封装的底层基础类库,这个类库就是需要我们进行深度分析的类库。
我们可以看到,在base.php
中加载完Loader.php
之后,会调用Loader::register();
方法,我们追踪进去可以看到这个方法
// 注册自动加载机制
public static function register($autoload = '')
{
// 注册系统自动加载
spl_autoload_register($autoload ?: 'think\\Loader::autoload', true, true);
$rootPath = self::getRootPath();
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 = get_declared_classes();
$composerClass = array_pop($declaredClass);
foreach (['prefixLengthsPsr4', 'prefixDirsPsr4', 'fallbackDirsPsr4', 'prefixesPsr0', 'fallbackDirsPsr0', 'classMap', 'files'] as $attr) {
if (property_exists($composerClass, $attr)) {
self::${$attr} = $composerClass::${$attr};
}
}
} else {
self::registerComposerLoader(self::$composerPath);
}
}
// 注册命名空间定义
self::addNamespace([
'think' => __DIR__,
'traits' => dirname(__DIR__) . DIRECTORY_SEPARATOR . 'traits',
]);
// 加载类库映射文件
if (is_file($rootPath . 'runtime' . DIRECTORY_SEPARATOR . 'classmap.php')) {
self::addClassMap(__include_file($rootPath . 'runtime' . DIRECTORY_SEPARATOR . 'classmap.php'));
}
// 自动加载extend目录
self::addAutoLoadDir($rootPath . 'extend');
}
这个方法其实就是注册自动加载的机制。他把Thinkphp的加载与Composer的加载封装到了一个文件,我们可以看到自动加载机制的方法里面,引入了Composer包里面的autoload_static.php
,并针对Composer的这种方式做自动加载,这里先不啰嗦,我们会在后续的章节中详细了解这个方法。
其他的框架在使用的时候,他们的逻辑是是一样的,都会在框架的第一步实现自动加载。
spl_autoload_register初学习
spl_autoload_register的使用
当我们去new一个找不到的class
时,PHP就会去自动调用sql_autoload_resister注册的函数,这个函数通过它的参数
传进去:
function load($className) {
require $className . '.php';
}
spl_autoload_register(load); // 将load函数注册到自动加载队列中
$db = new DB(); // 找不到DB类,就会自动去调用注册的load函数
多个spl_autoload_register的使用
spl_autoload_register是可以多次重复使用的,这一点正是解决了__autoload的短板,那么如果一个页面有多个,执行顺序是按照注册的顺序,一个一个往下找,如果找到了就停止。
function load1 ($className) {...}
function load2 ($className) {...}
...
spl_autoload_register('load1')
spl_autoload_register('load2')
命名空间映射
base.php
框架base.php文件,首先载入Loader类,并执行Loader的静态方法register,进行注册自动加载
// 载入Loader类
require __DIR__ . '/library/think/Loader.php';
// 注册自动加载
Loader::register();
可以自定义加载函数,并有register参数传入。
public static function register($autoload = '') {
// 注册系统自动加载
spl_autoload_register($autoload ?: 'think\\Loader::autoload', true, true')
}
Loader.php
由base.php中调用Loader的register静态方法。该方法首先注册一个自动加载函数。接着将框架的aplication,extend,composer的目录和各自的命名空间相映射
// 注册系统自动加载
spl_autoload_register($autoload ?: 'think\\Loader::autoload', true, true);
获取composer目录
$rootPath = self::getRootPath(); // 项目的根目录
self::$composerPath = $rootPath . 'vendor' . DIRECTORY_SEPARATOR . 'composer' . DIRECTORY_SEPARATOR;
载入composer自动加载文件,并将composer下载的安装包关系映射赋值给Loader属性。
$prefixLengthsPsr4,$prefixDirsPsr4两个属性描述了命名空间和实际目录位置的映射。当通过composer进行下载安装包时,会自动更新composer的autoload_static.php的属性
// Composer自动加载支持
if (is_dir(self::$composerPath)) {
if (is_file(self::$composerPath . 'autoload_static.php')) {
require self::$composerPath . 'autoload_static.php';
// get_declared_classes函数获取当前所有载入的类
$declaredClass = get_declared_classes();
$composerClass = array_pop($declaredClass);
foreach (['prefixLengthsPsr4', 'prefixDirsPsr4', 'fallbackDirsPsr4', 'prefixesPsr0', 'fallbackDirsPsr0', 'classMap', 'files'] as $attr) {
// 将composer的autoload_static.php类的prefixLengthsPsr4,prefixDirsPsr4属性赋值给Loader的prefixLengthsPsr4, prefixDirsPsr4属性
if (property_exists($composerClass, $attr)) {
self::${$attr} = $composerClass::${$attr};
}
}
} else {
self::registerComposerLoader(self::$composerPath);
}
}
由于composer的autoload_static.php中只定义了think\composer
app
和其他通过composer安装的安装包命名空间对于的目录的映射。所以还需要把框架核心的think``traits
命名空间的映射添加到Loader的$prefixLengthsPsr4,$prefixLengthsPsr4的属性上
// 注册命名空间定义
self::addNamespace([
'think' => __DIR__,
'traits' => dirname(__DIR__) . DIRECTORY_SEPARATOR . 'traits',
]);
将类库的映射文件中的数据,载入到Loader属性$classMap。类库映射文件是需要手动生成的,里面是所有类名映射到类的绝对地址。
// 加载类库映射文件
if (is_file($rootPath . 'runtime' . DIRECTORY_SEPARATOR . 'classmap.php')) {
self::addClassMap(__include_file($rootPath . 'runtime' . DIRECTORY_SEPARATOR . 'classmap.php'));
}
extend扩展目录的位置赋值给Loader类的$fallbackDirsPsr4。若自定义目录也想要自动加载。可以参考extend的方式。
// 自动加载extend目录
self::addAutoLoadDir($rootPath . 'extend')
最后在base.php文件中注册了类的别名。在使用一个不存在的类时,优先判断是否包含类的别名,在根据类的别名所对应的类来加载。
base.php
...
// 注册类库别名
Loader::addClassAlias([
'App' => facade\\App::class,
'Build' => facade\\Build::class,
'Cache' => facade\\Cache::class,
'Config' => facade\\Config::class,
'Cookie' => facade\\Cookie::class,
'Db' => Db::class,
'Debug' => facade\\Debug::class,
'Env' => facade\\Env::class,
'Facade' => Facade::class,
'Hook' => facade\\Hook::class,
'Lang' => facade\\Lang::class,
'Log' => facade\\Log::class,
'Request' => facade\\Request::class,
'Response' => facade\\Response::class,
'Route' => facade\\Route::class,
'Session' => facade\\Session::class,
'Url' => facade\\Url::class,
'Validate' => facade\\Validate::class,
'View' => facade\\View::class,
]);
到此,相关命名空间和目录的映射都准备好了。
类的自动加载
上节已经把命名空间和目录的映射都赋值给了Loader的相关属性了。此时在使用一个不存在的类时,就可以触发Loader的autoload方法进行自动加载了。
我们看一下上一节执行完成后,Loader类的属性值情况
class Loader {
...
$prefixLengthsPsr4 = [
"t" => [
"think\composer\" => 15,
"think\" => 6 ,
"traits\" => 7
],
"a" => [ "app\" => 4 ]
]
$prefixDirsPsr4 = [
"think\\composer\\" => " /Users/lihui/Documents/tp/tp5.1/vendor/composer/../topthink/think-installer/src ",
"app\\" => "/Users/lihui/Documents/tp/tp5.1/vendor/composer/../../application",
"think\\" => "/Users/lihui/Documents/tp/tp5.1/thinkphp/library/think",
"traits\\" => "/Users/lihui/Documents/tp/tp5.1/thinkphp/library/traits"
]
$fallbackDirsPsr4 = ["/Users/lihui/Documents/tp/tp5.1/extend"]
...
}
autoload方法实现自动加载
在代码中使用一个不存在的类时,会触发autoload方法,首页会查找$classAlias属性,是否定义了类的别名。如果定义了类的别名和实际类的映射。则可通过类的别名直接使用该类。主要是通过php内置函数class_alias函数实现
if (isset(self::$classAlias[$class])) {
return class_alias(self::$classAlias[$class], $class);
}
若没有定义类的别名和实际类的映射,则需要走命名空间的映射来找到实际类,并加载。
// 查找 PSR-4
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . '.php';
$first = $class[0];
if (isset(self::$prefixLengthsPsr4[$first])) {
foreach (self::$prefixLengthsPsr4[$first] as $prefix => $length) {
if (0 === strpos($class, $prefix)) {
foreach (self::$prefixDirsPsr4[$prefix] as $dir) {
if (is_file($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) {
return $file;
}
}
}
}
}
// 查找 PSR-4 fallback dirs
foreach (self::$fallbackDirsPsr4 as $dir) {
if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
return $file;
}
}
最终找到文件在系统中的位置,然后调用include 加载文件。
__include_file($file);
return true;