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

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

在剖析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系列:类的自动加载是如何设计的的更多相关文章

  1. thinkphp5源码剖析系列1-类的自动加载机制

    前言 tp5想必大家都不陌生,但是大部分人都停留在应用的层面,我将开启系列随笔,深入剖析tp5源码,以供大家顺利进阶.本章将从类的自动加载讲起,自动加载是tp框架的灵魂所在,也是成熟php框架的必备功 ...

  2. thinkphp学习笔记9—自动加载

    原文:thinkphp学习笔记9-自动加载 1.命名空间自动加载 在3.2版本中不需要手动加载类库文件,可以很方便的完成自动加载. 系统可以根据类的命名空间自动定位到类库文件,例如定义了一个类Org\ ...

  3. Yaf零基础学习总结5-Yaf类的自动加载

    Yaf零基础学习总结5-Yaf类的自动加载 框架的一个重要功能就是类的自动加载了,在第一个demo的时候我们就约定自己的项目的目录结构,框架就基于这个目录结构来自动加载需要的类文件. Yaf在自启动的 ...

  4. final关键字,类的自动加载,命名空间

    final关键字 1.final可以修饰方法和类,但是不能修饰属性: 2.Final修饰的类不能被继承: 3.Fina修饰的方法不能被重写,子类可以对已被final修饰的父类进行访问,但是不能对父类的 ...

  5. PHP面向对象学习-属性 类常量 类的自动加载 构造函数和析构函数 访问控制(可见性)

    在类的成员方法里面,可以用 ->(对象运算符):$this->property(其中 property 是该属性名)这种方式来访问非静态属性.静态属性则是用 ::(双冒号):self::$ ...

  6. PHP面向对象----- 类的自动加载

    1.类的自动加载 spl_autoload_register函数 test.php <?php spl_autoload_register('autoload'); // require_onc ...

  7. 浅析PHP类的自动加载和命名空间

    php是使用require(require_once)和include(include_once)关键字加载类文件.但是在实际的开发工程中我们基本上不会去使用这些关键字去加载类. 因为这样做会使得代码 ...

  8. ThinkPHP 3.2.3 自动加载公共函数文件的方法

    方法一.加载默认的公共函数文件 在 ThinkPHP 3.2.3 中,默认的公共函数文件位于公共模块 ./Application/Common 下,访问所有的模块之前都会首先加载公共模块下面的配置文件 ...

  9. Drupal如何实现类的自动加载?

    Drupal通过spl_autoload_register()注册类加载器实现自动加载: function _drupal_bootstrap_database() { // ... .... spl ...

随机推荐

  1. ES6的Iterator遍历器

    JS表示集合的对象主要有Array.Set.Object.Map,在以前,遍历它们需要使用2种不同的方法,而现在,JS提出了Iterator机制,可以给不同的数据结构提供统一的遍历方法,就是for…o ...

  2. C# 剪贴板中文乱码问题

    最近写了个小工具.主要功能是生成一些数据,然后一键复制,最终粘贴到需要的地方. 本来一切正常的,可是当把工具拿给朋友使用时,却被告知粘贴出来的数据是乱码.当时第一时间想到是区域问题(朋友是台湾人),不 ...

  3. 如何利用Oracle VM Templates 在几分钟内部署Oracle Real Application Clusters (RAC)

    本文未经授权,禁止一切形式的转载.如果对本文有任何疑问可以通过以下方式和我交流: 邮箱: jiangxinnju@163.com 博客园地址: http://www.cnblogs.com/jiang ...

  4. python 生成器和迭代器

    迭代器协议 1.迭代器协议是指:对象必须提供一个next方法,执行该方法要么返回迭代中的下一项,要么就引起一个Stoplteration异常,以终止迭代(只能往后走不能往前退) 2.可迭代对象:实现了 ...

  5. Sketch设计学习(一)

    很膜拜那些既能写出一手好代码,并且还能够懂设计懂交互的人.公司基本上都对这两块工作 分工分职,但是我觉得作为一名移动开发者,懂点设计 是必备的. 国外的很多大牛们,感觉他们虐我们千百遍了. 我觉得写代 ...

  6. containing block和BFC

    1.Box Box 是 CSS 布局的对象和基本单位,元素的类型和 display 属性,决定了这个 Box 的类型.比较常见的有: block-level box:display 属性为 block ...

  7. 【唯星宠物】——CSS/BootStrap/Jquery爬坑之响应式首页

    前言:唯星宠物产品官网,分为首页.子页和登录注册页三个页面,除网页内容设计与图片素材的部分使用网上的材料之外,其余内容呈现以及功能模块全部为自己重构. 一.响应式轮播banner 思路:使用BootS ...

  8. Linux库函数制作(静态库、动态库)

    Linux库函数制作(静态库.动态库) 静态库与动态库 链接方式 链接分为两种:静态链接.动态链接 静态链接: 由链接器在链接时将库的内容加入到可执行程序中 静态链接的特点是: 优点: 对运行环境的依 ...

  9. Ubuntu下比较通用的makefile实例

    本文转自http://blog.chinaunix.net/uid-20608849-id-360294.html  笔者在写程序的时候会遇到这样的烦恼:一个项目中可能会有很多个应用程序,而新建一个应 ...

  10. Python pip 下载速度慢? Windows 设置 国内源,用 阿里云 国内镜像 加速

    pip 提供了对 Python 包的查找.下载.安装.卸载的功能,是非常方便的 Python 包管理工具.但是,令人苦恼的是 pip 在国内的下载速度非常慢,速度常常只有每秒几十 K,甚至才几 K,小 ...