博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
thinkphp系列:类的自动加载是如何设计的
阅读量:5323 次
发布时间:2019-06-14

本文共 6566 字,大约阅读时间需要 21 分钟。

 

在使用框架开发时,可以发现框架有很多核心类,却很少看到显示的引入某个文件的代码,这是因为框架都采用了类的自动加载机制,即使用到类时,框架会自动找到该类所在文件的位置并引入该文件。

为了更容易看出代码思路,下面在说明时,只抽取了相关的主要代码。
在剖析thinkphp源码之前,先说说我做的一个项目实现的自动加载思路。
根据文件命名特点来确定文件所在的位置。
入口文件代码:

//入口文件index.php require_once('base.php'); if(function_exists('spl_autoload_register')) {
spl_autoload_register(array('Base', 'autoload')); } else {
function __autoload($class) {
return Base::autoload($class); } }

 

//base.php final class Base{
public static function autoload($class){
$class = strtolower($class);         if (ucwords(substr($class,0,5)) == 'Cache' && $class != 'cache'){
if (!@include_once(BASE_CORE_CACHE_PATH.DS.substr($class,0,5).'.'.substr($class,5).'.php')){
exit("Class Error: {$class}.isn't exists!"); } }elseif ($class == 'db'){
if (!@include_once(BASE_CORE_PATH.DS.'db'.DS.strtolower(DBDRIVER).'.php')){
exit("Class Error: {$class}.isn't exists!"); } }else{
if (!@include_once(.DS.$class.'.php')){
exit("Class Error: {$class}.isn't exists!"); } } } }

如代码所示,所用的类带Cache时,就从BASE_CORE_CACHE_PATH这里寻找类文件。默认就从这里寻找。

 

 

现在再让我们一起来看看thinkphp框架是怎么做的。

//start.php入口文件 namespace think; // ThinkPHP 引导文件 // 加载基础文件 require 'base.php'; // 执行应用 App::run()->send();
//base.php 文件 // 载入Loader类 require 'loader.php'; // 注册自动加载 \think\Loader::register();
//loader.php文件 namespace think; class Loader{         public static function register($autoload = '')     {
spl_autoload_register($autoload ?: 'think\\Loader::autoload', true, true); } // 自动加载 public static function autoload($class) {
echo 'enter autoload
'; echo $class[0].'
'; var_dump($class); } }

通过如上流程走下来,可以最终知道框架的自动加载功能实现主要在Loader类的autoload方法处。

以上代码可以自建一个小项目运行,假如项目名为test,浏览器里访问

http://localhost/test/start.php

可以得到如下结果:

enter autoload t string(9) "think\App"Fatal error:  Class 'think\App' not found in E:\xampp\htdocs\test\start.php on line 9

从结果可以判断出,当执行

App::run()->send();

此处代码时,框架已成功进入实现自动加载机制的方法里去了。

然后就仔细了解下thinkphp框架是怎么引入这个App类所在的文件。
将如上文件代码更加详细化,如下所示:

//start.php入口文件 namespace think; define('DS', DIRECTORY_SEPARATOR); define('EXT', '.php'); define('LIB_PATH', __DIR__ .DS.'library'.DS); // ThinkPHP 引导文件 // 加载基础文件 require 'base.php'; // 执行应用 App::run()->send();

接着新建App类文件,如果成功进入run方法,则表示App类自动加载成功。

'; } }

loader.php文件修改稍微多些,请看代码:

LIB_PATH . 'think' . DS, 'behavior' => LIB_PATH . 'behavior' . DS, 'traits' => LIB_PATH . 'traits' . DS, ]); var_dump(self::$prefixLengthsPsr4); } // 自动加载 public static function autoload($class) {
echo 'enter autoload
'; // echo $class[0].'
'; // var_dump($class); if ($file = self::findFile($class)) {
include($file); return true; } } /** * 查找文件 * @param $class * @return bool */ private static function findFile($class) {
// 查找 PSR-4 $logicalPathPsr4 = strtr($class, '\\', DS) . EXT; $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 . DS . substr($logicalPathPsr4, $length))) {
return $file; } } } } } } // 注册命名空间 public static function addNamespace($namespace, $path = '') {
if (is_array($namespace)) {
foreach ($namespace as $prefix => $paths) {
self::addPsr4($prefix . '\\', rtrim($paths, DS), true); } } else {
self::addPsr4($namespace . '\\', rtrim($path, DS), true); } } // 添加Psr4空间 private static function addPsr4($prefix, $paths, $prepend = false) {
if (!$prefix) {
} elseif (!isset(self::$prefixDirsPsr4[$prefix])) {
// Register directories for a new namespace. $length = strlen($prefix); if ('\\' !== $prefix[$length - 1]) { throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); } self::$prefixLengthsPsr4[$prefix[0]][$prefix] = $length; self::$prefixDirsPsr4[$prefix] = (array) $paths; } elseif ($prepend) {
} else {
} } }

base.php文件内容不变。
现在我们梳理一下代码执行顺序(从上往下依次执行):

---\think\Loader::register() ---spl_autoload_register('think\\Loader::autoload') ---\think\Loader::addNamespace() ---\think\Loader::addPsr4() ---\think\Loader::autoload() ---\think\Loader::findFile() ---App::run()

其中addNamespace里addPsr4方法将部分命名空间对应的实际目录存储进了static数组变量中。

打印$prefixLengthsPsr4和$prefixDirsPsr4这两个变量的内容,得到如下所示:

array(2) {  ["t"]=>  array(2) {    ["think\"]=>int(6)    ["traits\"]=>int(7)  }  ["b"]=>  array(1) {    ["behavior\"]=>int(9)  }}
array(3) {  ["think\"]=>  array(1) {    [0]=>string(34) "E:\xampp\htdocs\test\library\think"  }  ["behavior\"]=>  array(1) {    [0]=>    string(37) "E:\xampp\htdocs\test\library\behavior"  }  ["traits\"]=>  array(1) {    [0]=>    string(35) "E:\xampp\htdocs\test\library\traits"  }}

然后到了autoload里findFile这步,分析如下:

//之前测试autoload方法,得知$class = 'think\App'; $class[0] = 't'; //将$class字符串里反斜杠替换成文件分隔符, //再接上文件后缀,变成'think/App.php' //注意:$class变量里仍然是命名空间分隔符 $logicalPathPsr4 = strtr($class, '\\', DS) . EXT; $first = $class[0]; //$class = 'think\App'; //$prefix = 'think\';  if (isset(self::$prefixLengthsPsr4[$first])) {
foreach (self::$prefixLengthsPsr4[$first] as $prefix => $length) {
if (0 === strpos($class, $prefix)) {
foreach (self::$prefixDirsPsr4[$prefix] as $dir) {
//E:\xampp\htdocs\test\library\think路径前缀部分 + 文件分隔符 //+ think/App.php截取掉think/后余留下来的App.php if (is_file($file = $dir . DS . substr($logicalPathPsr4, $length))) {
return $file; } } } } }

这里注意2点:

1.这里prefix变量里反斜杠符号是在执行下面代码时加进去的。

self::addPsr4($prefix . '\\', rtrim($paths, DS), true);

加反斜杠的目的,是更加精确的确定命名空间,代表命名空间其中一段的终止。按如上代码逻辑,可防止与thinkme类似以think为前缀的命名空间发生混淆。

2.根据如上梳理总结寻找文件的主要思想:

文件位置 = 自定义命名空间对应的文件夹位置 + 文件分隔符 + 截取掉类的命名空间而余留下来的类名 + 文件后缀

以上梳理流程大大简化了thinkphp框架的自动加载机制,只选取了其中一种情况来进行剖析说明的。
日后有更多的理解,将会在此处进一步更新。
文中代码在github上有备份,欢迎下载。链接地址:

 

转载于:https://www.cnblogs.com/drunkhero/p/autoload_design.html

你可能感兴趣的文章
spring-使用MyEcilpse创建demo
查看>>
DCDC(4.5V to 23V -3.3V)
查看>>
kettle导数到user_用于left join_20160928
查看>>
activity 保存数据
查看>>
typescript深copy和浅copy
查看>>
linux下的静态库与动态库详解
查看>>
hbuilder调底层运用,多张图片上传
查看>>
深入理解基于selenium的二次开发
查看>>
较快的maven的settings.xml文件
查看>>
Git之初体验 持续更新
查看>>
Exception in thread "AWT-EventQueue-0" java.lang.IllegalThreadStateException
查看>>
随手练——HDU 5015 矩阵快速幂
查看>>
启动redis一闪就关
查看>>
Maven之setting.xml配置文件详解
查看>>
ASP.NET 4.5 Web Forms and Visual Studio vs2013年入门1
查看>>
SDK目录结构
查看>>
malloc() & free()
查看>>
HDU 2063 过山车
查看>>
高精度1--加法
查看>>
String比较
查看>>