分享web开发知识

注册/登录|最近发布|今日推荐

主页 IT知识网页技术软件开发前端开发代码编程运营维护技术分享教程案例
当前位置:首页 > 运营维护

thinkphp系列:类的自动加载是如何设计的

发布时间:2023-09-06 01:28责任编辑:苏小强关键词:thinkphp

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

为了更容易看出代码思路,下面在说明时,只抽取了相关的主要代码。

在剖析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(BASE_LIB_PATH.DS.$class.‘.php‘)){
exit("Class Error: {$class}.isn‘t exists!");
}
}
}
}

如代码所示,所用的类带Cache时,就从BASE_CORE_CACHE_PATH这里寻找类文件。默认就从BASE_LIB_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<br>‘;
???echo $class[0].‘<br>‘;
???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类自动加载成功。

<?php
//library/think/App.php
namespace think;
class App
{
???public static function run()
???{
???echo ‘enter run<br>‘;
???}
}

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

<?php
//loader.php文件
namespace think;
class Loader{

// PSR-4
???private static $prefixLengthsPsr4 = [];
???private static $prefixDirsPsr4 ???= [];

public static function register($autoload = ‘‘)
{
spl_autoload_register($autoload ?: ‘think\\Loader::autoload‘, true, true);

// 注册命名空间定义
???????self::addNamespace([
???????????‘think‘ ???=> 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<br>‘;
???// echo $class[0].‘<br>‘;
???// 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://github.com/bingodawson/tpautoload.git


thinkphp系列:类的自动加载是如何设计的

原文地址:http://www.cnblogs.com/drunkhero/p/autoload_design.html

知识推荐

我的编程学习网——分享web前端后端开发技术知识。 垃圾信息处理邮箱 tousu563@163.com 网站地图
icp备案号 闽ICP备2023006418号-8 不良信息举报平台 互联网安全管理备案 Copyright 2023 www.wodecom.cn All Rights Reserved